Skip to content

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 .riv file Lupian sent you.
  • The animation loads locally but fails on staging/production.
  • You need a new version of the .riv to 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/)

js
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=2 query.
  • Good default for small files (a few hundred KB or less).

2. On your CDN / static host

js
new Rive({ src: "https://cdn.your-site.com/rive/hero.riv", canvas, autoplay: true });
  • Long-cache friendly. Set Cache-Control: public, max-age=31536000, immutable and 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

js
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:

HostWhere to set CORS
Cloudflare R2Bucket → Settings → CORS Policy
AWS S3Bucket → Permissions → CORS configuration (JSON)
Cloudflare CDNTransform Rule → Modify Response Headers
Vercelvercel.jsonheaders block
Netlify_headers file or netlify.toml
Nginxadd_header Access-Control-Allow-Origin ...;

Vercel example (vercel.json):

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:

  1. Change the filename when content changeshero.v2.riv, hero-2026-05-21.riv, or hero.<hash>.riv.
  2. Set Cache-Control: public, max-age=31536000, immutable on the file.
  3. 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.

js
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 .riv is still loading.
  • If onLoadError fires.
  • If the browser has no WebGL.
html
<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

  • .riv on a different origin without CORS — fails silently in some browsers, with a clear console error in others.
  • No Cache-Control headers — 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-eval in CSP — runtime never starts.

If stuck, send this to Lupian

  • The full .riv URL (so we can curl -I it).
  • The page URL.
  • Network tab screenshot showing the .riv response headers.
  • Console errors.
  • See Ask Lupian for help.

Developer support for Lupian-delivered Rive animations