ESC

Type to search the knowledge base.

HTTP Caching Headers

Cache-Control, ETag, Last-Modified, Vary, and immutable fingerprinting patterns for static vs HTML.

intermediate3 min read
  • browser
  • http-cache
  • cache-control
  • etag
  • performance

The fastest request is one the browser never sends. HTTP caching is controlled primarily by response headers. Frontend builds (content hashes) and CDN config make or break production performance and “users stuck on old JS” incidents.

Docs: MDN HTTP caching, web.dev HTTP cache, RFC 9111.

Cache-Control essentials

Cache-Control: max-age=31536000, immutable
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: private, max-age=0, must-revalidate
Directive Meaning
max-age=N Fresh for N seconds from response
immutable Won’t revalidate on reload while fresh (great for hashed assets)
no-cache Store but revalidate before use
no-store Don’t store (sensitive)
private Browser OK; shared CDN should not store
public Shared caches may store
stale-while-revalidate Serve stale while revalidating (where supported)

Fingerprinted static assets (best pattern)

Build emits /app.9f3c2a.js. Deploy headers:

Cache-Control: public, max-age=31536000, immutable

HTML references the new hash on release → clients fetch new file; old hash can stay cached forever. See Caching static assets.

HTML / app shell

Usually short freshness or revalidate so deploys propagate:

Cache-Control: no-cache
ETag: "abc123"

no-cache still allows storing; browser checks with conditional requests.

Validators: ETag and Last-Modified

ETag: "v3-deadbeef"
Last-Modified: Tue, 04 Aug 2026 12:00:00 GMT

Browser revalidates:

If-None-Match: "v3-deadbeef"

Server: 304 Not Modified without a body when unchanged — saves download, still costs a round trip.

Vary

Vary: Accept-Encoding
Vary: Origin

Caches must key on listed request headers. Wrong Vary causes cache poisoning or cache misses. CORS responses that differ by Origin should Vary: Origin.

Heuristic caching

Without explicit headers, some caches guess from Last-Modified. Don’t rely on heuristics for SPAs — set explicit Cache-Control.

Service workers vs HTTP cache

SW caches API is separate. A fetch may hit HTTP cache underneath depending on request mode and SW strategy. Debug with Network panel “size” column (disk/memory cache vs SW).

Common incidents

  1. HTML cached forever → users stuck on old entry that points at deleted hashed files.
  2. JS without hash + long max-age → same stuck state.
  3. no-store on everything → slow, expensive, no offline.
  4. CDN ignores private misconfig → shared user data (security bug).

Interview out-loud

“Hashed static assets get long max-age and immutable. HTML revalidates with no-cache or short max-age plus ETag. Vary must match response variance. Caching bugs are usually HTML cached too long or assets without fingerprints cached too long.”

Deploy smoke tests

After each release: open Network, verify HTML is revalidated or short-cached, and /assets/*hash* returns immutable long cache. Hit an old hashed URL intentionally — 404 is better than wrong content. For CDNs with layered caches, purge HTML on release; don’t rely on multi-hour HTML TTLs for apps that ship multiple times a day.

Further depth

Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.

Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.

Further reading

Related guides