← Thin Margins

The three Cache-Control headers I actually use

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?

1. Fingerprinted assets: cache forever

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.

2. HTML: always ask, usually get a 304

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.

3. Everything else: short, with a grace period

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.

Checking what you actually send

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.