Hosting assets & CORS
Where .riv files, fallback images, and referenced assets (fonts, images) live, and what cache/CORS headers to set.
Use this when
- You are deciding where to put the
.rivfile Lupian sent you. - The animation loads locally but fails on staging/production.
- You need a new version of the
.rivto ship without users seeing the old one.
What you need
- A place to host static files: your own server, your app bundle's
public/folder, a CDN (Cloudflare, Vercel, Netlify, S3+CloudFront, etc.). - Permission to set HTTP headers on that origin (for CORS and cache control).
Three places a .riv can live
1. In your app bundle (public/)
new Rive({ src: "/animations/hero.riv", canvas, autoplay: true });- Same origin → no CORS to worry about.
- Cache-busted automatically by most bundlers if you import it, or use a
?v=2query. - Good default for small files (a few hundred KB or less).
2. On your CDN / static host
new Rive({ src: "https://cdn.your-site.com/rive/hero.riv", canvas, autoplay: true });- Long-cache friendly. Set
Cache-Control: public, max-age=31536000, immutableand change the filename (hero.v2.riv) when content changes. - Requires CORS if loaded from a different origin than the page. See below.
3. Pre-fetched as ArrayBuffer
const buf = await fetch("/animations/hero.riv").then(r => r.arrayBuffer());
new Rive({ buffer: buf, canvas, autoplay: true });- Useful when you want to control timing (e.g. preload before showing a section).
CORS — fix the most common production failure
If your .riv is on a different origin than the page that loads it, the host must respond with:
Access-Control-Allow-Origin: https://your-site.com(or * for fully public assets).
Quick reference per host:
| Host | Where to set CORS |
|---|---|
| Cloudflare R2 | Bucket → Settings → CORS Policy |
| AWS S3 | Bucket → Permissions → CORS configuration (JSON) |
| Cloudflare CDN | Transform Rule → Modify Response Headers |
| Vercel | vercel.json → headers block |
| Netlify | _headers file or netlify.toml |
| Nginx | add_header Access-Control-Allow-Origin ...; |
Vercel example (vercel.json):
{
"headers": [{
"source": "/(.*)\\.riv",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
}]
}Symptom of a missing CORS header: the .riv URL returns 200 in DevTools' Network tab, but the page console says "blocked by CORS policy" and onLoadError fires. See Animation will not load.
Cache strategy
.riv files are immutable for a given version. The safest pattern:
- Change the filename when content changes —
hero.v2.riv,hero-2026-05-21.riv, orhero.<hash>.riv. - Set
Cache-Control: public, max-age=31536000, immutableon the file. - Update your code to reference the new filename.
This way users never see a stale .riv and the browser never re-fetches a still-valid one.
If you cannot rename, use a query param: hero.riv?v=2. Browsers treat the query as part of the cache key.
Referenced fonts / images
Some .riv files use referenced asset mode — the font or image lives outside the .riv and is fetched at runtime. handoff.md will tell you if this applies.
new Rive({
src: "/animations/hero.riv",
canvas,
autoplay: true,
assetLoader: (asset, bytes) => {
// bytes.length === 0 means "referenced" — you fetch it.
if (asset.isFont && bytes.length === 0) {
fetch(`/fonts/${asset.name}.woff2`)
.then(r => r.arrayBuffer())
.then(b => asset.decode(new Uint8Array(b)));
return true;
}
return false; // let the runtime handle embedded/hosted assets
},
});Most Lupian deliveries embed assets — you only need this if handoff.md lists referenced fonts or images. Full API: rive.app/docs.
CSP (Content Security Policy)
If your site sets a CSP header, allow WASM:
script-src 'self' 'wasm-unsafe-eval';Without wasm-unsafe-eval, the runtime fails to initialise. See Animation will not load.
The fallback image
Host the fallback image (PNG/SVG) the same way as the .riv. Show it:
- While the
.rivis still loading. - If
onLoadErrorfires. - If the browser has no WebGL.
<div class="rive-mount" style="position:relative;">
<img src="/animations/hero.fallback.png" alt="" style="width:100%; height:100%;">
<canvas id="rive-canvas" style="position:absolute; inset:0; opacity:0;"></canvas>
</div>
<script type="module">
// fade canvas in on load, leave image visible on error
</script>Common mistakes
.rivon a different origin without CORS — fails silently in some browsers, with a clear console error in others.- No
Cache-Controlheaders — users get a fresh fetch every visit, slowing the first paint. - Same filename across versions — users see the old animation until they clear cache.
- Forgetting
wasm-unsafe-evalin CSP — runtime never starts.
If stuck, send this to Lupian
- The full
.rivURL (so we cancurl -Iit). - The page URL.
- Network tab screenshot showing the
.rivresponse headers. - Console errors.
- See Ask Lupian for help.
Related
- Animation will not load
- Embedding options
- Rive asset loading API: rive.app/docs

