Caching is the cheapest performance win on the web: the fastest request is the one you never make. But caching is also where subtle bugs hide — a stale asset that will not update, or a cache that never kicks in. Understanding the headers that govern it lets you make a site fast and keep it correct.
Fresh versus stale
The core idea is freshness. A cached response is "fresh" for a period you define; while fresh, it is served instantly without contacting the server. Once "stale", the cache must check with the server before reusing it. Getting this lifetime right is the whole game.
Cache-Control: the main lever
- max-age=3600 — fresh for one hour.
- no-cache — may be stored, but must be revalidated before use.
- no-store — never cache at all (for sensitive data).
- public vs private — whether shared caches (CDNs) may store it, or only the user’s browser.
- immutable — the content will never change, so never revalidate.
Validation with ETags
When a cached item goes stale, the browser does not always need to re-download it. An ETag is a fingerprint of the content; the browser sends it back with If-None-Match, and if the content has not changed, the server replies 304 Not Modified with no body. That turns a full download into a tiny check. Content hashes make excellent ETags, since identical content always produces the same fingerprint.
The cache-busting pattern
The most reliable strategy for static assets is to cache them forever (a long max-age, immutable) but include a content hash or version in the filename, like app.4f3a2b.js. When the content changes, the filename changes, so browsers fetch the new file while still caching the old one aggressively. This gives you both speed and instant updates.