Caching

Decide, per response, whether it may be stored and for how long — and stop shipping stale CSS.

06of 18
≈ 14Min read
5Parts

The fastest request is the one that is never made

A cached response costs nothing: no DNS, no connection, no round trip. Caching is the largest performance win available to a web page, and it is entirely controlled by a header you write.

There are two questions, and they are separate. May this be stored? And how long is it good for? no-store answers the first. max-age answers the second. Confusing them is how a page ends up either uncacheable or wrong.

Cache-Control, in the directives that matter

DirectiveMeans
no-storenever write this down anywhere — for personal or secret data
no-cachestore it, but always ask before reusing it (a revalidation, not a full download)
max-age=3600fresh for an hour; after that it must be revalidated
immutablewhile fresh, never ask again — even on a reload
must-revalidateonce stale, never serve it while offline
privatea browser may store it, a shared CDN may not
publica shared cache may store it even if it looks personal
s-maxage=60a different lifetime for shared caches (CDNs)
no-cache does not mean "do not cache"

It means "do not use it without asking". The one that forbids storing is no-store. Half the misconfigured sites on the web have this pair the wrong way round.

The two patterns that cover almost everything

A content hash in the file name turns caching into a solved problem: the URL is a promise that the bytes never change, so it can be cached for a year, and a deploy simply references a different URL. Every bundler does this by default.

The document that points at those assets must not be cached hard, or a visitor keeps the old HTML and never learns the new asset names. no-cache gives you a revalidation — usually a 304 with no body, which is a few hundred bytes.

a hashed asset, and a document
# /assets/app.8f3d21.css — the name changes when the content does
Cache-Control: public, max-age=31536000, immutable

# /index.html — must always be current, but need not always be re-downloaded
Cache-Control: no-cache

# /api/me — personal
Cache-Control: no-store

Where things are cached

A CDN purge does nothing to the copy in a browser. That is why a hard cache lifetime is a promise you cannot take back, and why hashed file names matter more than any purge button.

CacheSeesCleared by
the browsereverything for that userthe user, or an expiry
a CDN / shared proxyeverything not marked privateyour purge command
a service workerwhatever your code puts thereyour code — see that lesson

Vary: the cache key

A cache stores one entry per URL plus the request headers named in Vary. Forgetting it serves the wrong language to somebody; Vary: Cookie on a public page makes every logged-in user their own cache entry and throws the hit rate away.

one URL, several answers
Vary: Accept-Encoding            # the compressed and uncompressed copies differ
Vary: Accept-Language            # …and the Azerbaijani and English ones
Vary: Cookie                     # effectively uncacheable in a shared cache

Practice