Using immutable for Versioned Static Assets
Learn how the immutable directive prevents unnecessary revalidation requests for content-hashed static assets, improving reload performance significantly.
Detailed Explanation
The immutable Directive
immutable tells the browser: this response will never change during its freshness lifetime, so don't bother revalidating even on explicit reload.
The Problem Without immutable
Modern build tools (webpack, Vite, esbuild) generate filenames with content hashes:
app.a1b2c3d4.js
styles.e5f6g7h8.css
logo.i9j0k1l2.png
When the content changes, the filename changes. The old filename literally never changes — it becomes a new file. Despite this, when users press F5 or Ctrl+R, browsers send conditional revalidation requests (If-None-Match) for every cached resource, even with a long max-age.
These requests always return 304 Not Modified because the content hasn't changed. They waste bandwidth and add latency to page reloads.
The Solution
Cache-Control: public, max-age=31536000, immutable
With immutable, the browser skips revalidation entirely on reload. The resource is served from cache instantly — zero network requests.
Performance Impact
For a typical SPA with 20+ static assets:
- Without immutable: Reload sends 20+ conditional requests (even if all return 304)
- With immutable: Reload serves all from cache with zero network activity
The improvement is most noticeable on high-latency connections (mobile networks, distant servers).
Browser Support
immutable is supported by Firefox (since v49) and Safari (since v11). Chrome does not support it but has its own heuristic that achieves similar behavior for resources with very long max-age values. Including it is still recommended — browsers that don't recognize it simply ignore it.
When NOT to Use immutable
Never use immutable on resources without content hashes in their filenames. If /styles.css changes but keeps the same URL, immutable will cause browsers to serve the old version until max-age expires.
Use Case
A React application built with Vite outputs files like 'index-Bk3d9f2a.js' and 'index-H8x4n1p.css'. Setting 'Cache-Control: public, max-age=31536000, immutable' on these assets means users never re-download them unless the content actually changes (which produces a new filename). On a mobile connection with 200ms latency, skipping 20 conditional requests saves 4 seconds on page reload.