The page you see and the page an auditor sees are not necessarily the same response. Between the origin and the auditor sit a browser cache, a CDN, a framework cache, and sometimes the audit tool’s own storage. Each one is allowed to keep a copy for as long as the cache headers say. When the headers say "a day", a deploy does not change the copies that already exist.
This site learned that the hard way on 2026-10-23: the origin was updated, and an external audit read an old version because the HTML headers permitted intermediaries to serve a stale copy. This article is about what those headers mean, what an auditor actually receives, and why the fix was to make HTML revalidate rather than to purge harder.
The layers between you and the reader
| Layer | What it holds | What controls it |
|---|---|---|
| Browser cache | A copy on the reader’s device. | Cache-Control (the max-age part). |
| CDN / edge cache | A copy at the nearest point of presence. | s-maxage, stale-while-revalidate, CDN rules. |
| Framework cache | A prerendered page held by the app runtime. | Framework config and the response headers it emits. |
| Audit tool | Its own snapshot of what it fetched. | Nothing you can set; it may not refetch on demand. |
Only the first three are influenced by your headers, and the third-party tool is the one that reports what it saw. That asymmetry is the trap: you can verify the origin and still fail to verify the experience.
The directives that matter
max-age=N— a browser may reuse the response for N seconds without asking.s-maxage=N— a shared cache (CDN) may reuse it for N seconds. This is the directive that produces "the deploy is live but the edge still serves the old page".stale-while-revalidate=N— a cache may serve the stale copy for N seconds while it revalidates in the background. It is a performance feature and a permission to be out of date.no-store— do not store the response at all. Correct for per-request pages and APIs.must-revalidate— once stale, the cache must check with the origin rather than serving the old copy.ETag/304— the cheap revalidation: a request comes in, the origin says "unchanged", and the body is not resent.
What this site had, and what it changed
Before the fix, content HTML carried s-maxage=86400 and stale-while-revalidate=604800. In plain terms: a shared cache could serve a copy for a day, and keep serving a stale copy for up to a week while revalidating. For a site whose product argument is "the published version is the running version", that is the wrong trade. An external audit read a version that no longer matched the origin, and the report was written against the old page.
The fix was to stop granting the permission. HTML now carries public, max-age=0, must-revalidate, and the ETag makes the revalidation a 304 rather than a full response. stale-while-revalidate is gone from HTML entirely. Static assets under /_next/static/ keep a long cache, which is correct because their filenames are content-hashed: a new build produces new URLs, so an old copy can never be the wrong version of a current URL.
Per-request pages are the other direction: /report/ is generated from the visitor’s own domain and /api/ returns live data, so both are no-store. Caching one visitor’s scan and serving it to the next would be a correctness bug, not a performance trade.
What an auditor actually sees
"I deployed it" answers a question about the origin. The auditor’s question is "what did you receive, and when". The useful evidence is the response they got: status, Cache-Control, Age, ETag, and the content itself. A response with a large Age and an old title is an old copy, regardless of what the origin has now.
# 1. What headers does the deployed page return? curl -sS -D - -o /dev/null https://your-domain.com/ # 2. Fetch twice and compare Age / ETag curl -sS -D - -o /dev/null https://your-domain.com/ | grep -iE 'cache-control|age|etag|cf-cache-status' # 3. Does the body contain the version marker you expect? curl -sS https://your-domain.com/ | grep -oE '<title>[^<]+'
The second command is the one that distinguishes a cache hit from a fresh response. If an intermediary reports HIT with a growing Age, the response did not come from the origin; the origin’s state is not what the reader saw.
The tests that keep it fixed
A header policy that is only written down drifts. This repository asserts the cache header incheck:live, so a deploy that accidentally restores a long s-maxage on HTML fails the smoke check rather than waiting for an external report. The deploy also purges the edge on a best-effort basis, because a header change and a deploy can race with a copy that was already cached.
The general lesson is worth stating separately from this site: tests that fetch the origin are not tests of what a reader receives. If the product claims a version is live, the test has to look at the response the way an outsider would, including the cache headers.
The trade, stated plainly
Revalidating HTML costs a round trip per view. Keeping static assets immutable costs nothing because their URLs change with the content. Caching per-request pages would be a correctness bug. The choice is not "performance versus correctness"; it is deciding which content can tolerate being old and for how long, and writing that decision into the headers instead of hoping the intermediary agrees.
For a site that publishes a number, a score, or a date, "old" is a wrong answer. That is the whole reason the header changed.