2026-10-11
Most caching advice lists every directive the spec allows. In practice a static site needs three policies, chosen by one question: can the URL change its content without changing its name?
If the build tool puts a content hash in the file name (app.3f9c2e.js), the URL and the
bytes are tied together. A new version gets a new name, so the old one can be cached indefinitely.
Cache-Control: public, max-age=31536000, immutable
immutable tells the browser not to revalidate even when the user presses reload.
Without it, a reload sends a conditional request for every asset on the page, and each one comes back
304 Not Modified after a full round trip.
HTML pages keep their URL while their content changes, and they are what points to the fingerprinted assets. They must never be served stale for long.
Cache-Control: no-cache ETag: "5f1d-62a8c0b4"
no-cache does not mean "do not store". It means "store, but check with the server before
each use". With an ETag the check is cheap: the browser sends
If-None-Match, and the server replies 304 with an empty body when nothing changed.
no-store is the directive that forbids storing, and it only belongs on responses with
personal data.
Images without a hash, feeds and JSON that changes a few times a day sit in between:
Cache-Control: public, max-age=300, stale-while-revalidate=86400
For five minutes the cached copy is used as is. After that, and for up to a day, the browser may still show the cached copy immediately while it fetches a fresh one in the background. The visitor never waits for the network, and the content is at most one visit out of date.
curl -sI https://example.com/ | grep -i -E 'cache-control|etag|last-modified'
Reverse proxies and CDNs sometimes add or rewrite these headers, so check the response the browser receives, not the configuration file.