| name | frontend-asset-management |
| description | Use when adding, referencing, or serving static assets (images, fonts, videos, 3D models) through the R2 CDN pipeline with type-safe imports |
Frontend: Asset Management
Static assets are content-hashed, synced to Cloudflare R2, and served via CDN. All asset references MUST be type-safe through the generated manifest.
Developer Workflow
1. Add file → assets/{folder}/{file}
2. Generate → pnpm fe:assets:gen
3. Use in code → Assets_getAssetUrl("folder/file.png")
4. Sync to R2 → pnpm fe:assets:push
Every step is mandatory. Skipping step 2 causes TypeScript errors. Skipping step 4 means assets won't load in deployed environments.
Directory Structure
spark/frontend/my-vite-app/
├── assets/ # Source assets (committed to git)
│ ├── logo.svg
│ └── Page_Login/
│ ├── carousel-bg.png
│ └── slide-1.png
├── scripts/
│ ├── generate-asset-manifest.js # Step 2: generates types
│ └── sync-assets-to-r2.js # Step 4: uploads to R2
└── src/types/
└── assets.types.ts # Auto-generated (DO NOT edit manually)
Folder convention: Organize by page or feature, matching the Page_ naming convention (e.g., assets/Page_Login/, assets/Page_Dashboard/). Shared assets go in the root assets/ folder.
Type-Safe Asset Usage
import { Assets_getAssetUrl, type Assets_AssetPath } from "@/types/assets.types";
const url = Assets_getAssetUrl("Page_Login/slide-1.png");
const slides: { image: Assets_AssetPath }[] = [
{ image: "Page_Login/slide-1.png" },
];
<div style={{ backgroundImage: `url(${Assets_getAssetUrl("Page_Login/carousel-bg.png")})` }} />
<img src={Assets_getAssetUrl("logo.svg")} alt="Spark" />
Generated Exports (assets.types.ts)
| Export | Purpose |
|---|
Assets_AssetPath | Union type of all valid asset paths |
Assets_MANIFEST | Map: original path → hashed R2 key |
Assets_METADATA | Map: path → { hash, size, mimeType } |
Assets_getAssetUrl(path) | Full URL (dev: /assets/..., prod: CDN) |
Assets_getHashedPath(path) | Hashed R2 key only |
Assets_getMetadata(path) | Metadata for an asset |
Environment Behavior
| Environment | URL pattern |
|---|
| Development | /assets/{original-path} (Vite dev server) |
| Production | {VITE_CDN_BASE_URL}/{env}/assets/{hashed-filename} |
Content hashing (logo.0206c431.svg) provides automatic cache busting — 1-year immutable cache headers on R2.
pnpm Scripts
pnpm fe:assets:gen
pnpm fe:assets:push
Common Mistakes
- ❌ Hardcoding asset paths:
"/assets/logo.svg" → ✅ Assets_getAssetUrl("logo.svg")
- ❌ Editing
assets.types.ts manually → ✅ Run pnpm fe:assets:gen
- ❌ Adding assets without regenerating → ✅ Always run gen after adding/removing files
- ❌ Using
<img src={require(...)}> or Vite import for assets → ✅ Use Assets_getAssetUrl()
- ❌ Referencing assets not in
assets/ folder → ✅ All static assets go through the pipeline
Supported File Types
Images (PNG, JPG, GIF, WebP, SVG, AVIF), Fonts (WOFF, TTF, OTF), Videos (MP4, WebM, MOV, AVI), Audio (MP3, WAV, OGG, FLAC, M4A), 3D Models (GLB, GLTF, FBX, OBJ, USDZ), Documents (PDF, TXT, CSV, JSON, XML).
Related Skills
- cloudflare-wrangler-cli — R2 bucket and Workers deployment
- frontend-environment-variables —
VITE_CDN_BASE_URL configuration