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