Glossary
Cache-Control
`Cache-Control` is the response header that states who may store a response, for how long, and under what conditions it must be revalidated. It is the single most consequential performance header: a correctly cached asset is not requested at all on a repeat visit, which is faster than any amount of optimisation applied to fetching it. The policies that suit a versioned static file and an HTML document are opposite, and applying one to the other causes the usual problems.
The directives that matter
`max-age=<seconds>` — how long the response may be reused without asking again.
`public` and `private` — whether shared caches such as CDNs may store it, or only the visitor's own browser. Anything personalised must be `private`.
`no-cache` — store it, but revalidate before each reuse. It does not mean do not cache, which is the most common misreading.
`no-store` — do not write it anywhere. This is the one that genuinely prevents caching, and it belongs on responses containing personal data.
`immutable` — this will never change during its lifetime, so do not revalidate even when the user reloads.
`stale-while-revalidate=<seconds>` — serve the stale copy immediately and refresh in the background, which removes the latency of revalidation entirely.
Two policies for two kinds of resource
Versioned static assets — a file whose name contains a content hash — can never change without the name changing. `public, max-age=31536000, immutable` is the right answer: a year, never revalidated.
HTML documents change in place at the same URL. They need a short `max-age`, or `no-cache` with a validator, so a publication is visible promptly.
Getting these the wrong way round produces the two classic failures: a year-long cache on HTML that shows visitors a page you replaced weeks ago, and no cache on assets that are re-downloaded on every visit.
Responses that are personalised or authenticated need `private` at minimum, and `no-store` where the content is sensitive.
Validators and conditional requests
When a cached response expires, the client can revalidate rather than re-download, using `ETag` or `Last-Modified` from the original response.
If nothing changed, the server answers `304 Not Modified` with no body, which costs a round trip and no bandwidth.
This is why a short `max-age` is not as expensive as it sounds — the repeated requests mostly return 304.
`stale-while-revalidate` improves on it further by not making the visitor wait for the revalidation at all.
How to check a site's caching
`curl -I https://example.com/asset.js | grep -i 'cache-control\|etag\|last-modified'` shows the policy and the validators for one resource.
Check HTML and static assets separately, since they should differ. Identical headers on both is a sign the policy was set once at the server root.
In the browser's network panel, reloading shows which resources came from cache and which were re-fetched, which is the practical view.
Check behind the CDN as well. The edge may apply its own policy, and the header a visitor receives is the one that counts.
Frequently asked questions
- What does no-cache actually mean?
- Store the response, but revalidate with the server before reusing it. The directive that prevents storage entirely is no-store.
- How long should static assets be cached?
- A year, with immutable, provided the filename contains a content hash so a change produces a new URL.
- Why do visitors still see my old page?
- Almost always a long max-age applied to HTML. Documents that change at the same URL need a short lifetime or revalidation.
Sources
Related
VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.
Scan a website