immutable to the service worker, and describes how to verify after every deploy which version actually reaches the visitor.Why the new version is live and visitors still see the old one
A deploy feels like a switch: old before, new after. Technically it only describes the state of the origin server. Everything between that server and the browser keeps working by its own rules - and those rules are designed to reuse a stored response for as long as possible. That is exactly what a cache is for. In an analysis of millions of desktop responses, 62.2 percent carried a max-age directive, 16.5 percent no-cache and 9.6 percent no-store (HTTP Archive Web Almanac 2021). The majority of responses on the web actively say: hold on to me for a while.
The decisive point is written into the specification itself. The HTTP caching standard offers no way for a server to remove an already stored response from someone else's cache (RFC 9111). Invalidation is foreseen only as a side effect: a successful request with a modifying method against the same address invalidates the stored response (RFC 9111). A deploy is not such a request. It changes files on the server, not the storage in the browser of a visitor who opened the page two hours ago. Treating a deploy as finished the moment the origin server has been switched over means overlooking the second half of the route.
Caching is one of the most effective single measures for short load times, and this article does not argue against it. The guide to using a CDN covers that side in detail. It only becomes a problem where the freshness lifetime and the rate of change drift apart: a file that changes every two weeks but is served as valid for a year is no longer an optimisation, it is an open account. The task is to set every layer deliberately - and to re-measure after every deploy whether it does what it says on paper.
The five layers between deploy and visitor
Between the moment the new version sits on the origin server and the moment a human sees it, there are typically five storage locations. They differ not only technically, but above all in who can clear them and how long they otherwise persist. That question of responsibility is the real core of the problem: operations can restart the reverse proxy and trigger a purge request at the CDN, but nobody inside the company can touch the storage on someone else's device.
| Layer | What it stores | Who clears it | Typical lifetime |
|---|---|---|---|
| Browser cache | files and sometimes HTML on the visitor's device | only the visitor | until max-age expires |
| Service worker | its own copy in the site's storage | the worker lifecycle | until the new build activates |
| CDN node | responses held separately per location | a purge request or s-maxage | different per node |
| Reverse proxy | fully rendered pages in front of the application | operations during the deploy | usually minutes to hours |
| Application cache | fragments, queries, rendered building blocks | the application code | until invalidation |
The table explains why cache faults are so hard to reproduce. A colleague in the office sees the new build because their CDN node has already been refreshed; a customer in another region sees the old one because their node still holds a valid copy. Both observations are correct in themselves. Anyone who wants to capture such differences systematically needs check points in several places - the article on monitoring from several locations describes the approach. And because clearing the layers is spread across several people, the question of who may purge which cache belongs in the same order as the rest of the access rights in the monitoring team.
What the numbers say about cache headers on the web
The distribution of cache directives on the open web shows how varied practice is. Alongside max-age at 62.2 percent, on desktop public reached 29.7 percent, must-revalidate 12.2 percent, private 9.5 percent and immutable only 3.5 percent of responses (HTTP Archive Web Almanac 2021). The directives that would be particularly useful around deploys also remain rare: s-maxage, which lets you steer CDN freshness separately from browser freshness, appeared on 1.6 percent of responses, stale-while-revalidate on 2.3 percent (HTTP Archive Web Almanac 2021). The latter allows an expired response to be served while it is refreshed in the background (RFC 5861) - helpful for load times, but one more reason why old content stays visible longer than expected.
The second finding concerns the length of the lifetime. In a 2020 analysis of mobile pages, 60.2 percent of resources had a cache lifetime set too short relative to the actual age of the content; the median gap between lifetime and age was 25 days (HTTP Archive Web Almanac 2020). Lifetimes that are too short cost load time, lifetimes that are too long cost currency. The opposite direction is documented as well: in the 2019 analysis, 1.1 percent of responses carried max-age=0 without no-store (HTTP Archive Web Almanac) - they may be stored, but must be revalidated before every reuse. And the Vary header, which defines which request characteristics produce a separate variant, appeared on only 46.25 percent of the requests audited (HTTP Archive Web Almanac 2021). Without it, a shared cache can pass the wrong language or format variant on to the next visitor.
The 2019 analysis found more than 1,500 erroneous cache directives on 0.28 percent of responses, including spellings such as nocache or maxage (HTTP Archive Web Almanac 2019). Unknown directives are simply ignored under the standard (RFC 9111). The response then behaves as if it carried no directive at all - and the cache is free to make its own estimate. The standard names a widespread approach: treat 10 percent of the interval since the last modification as the freshness lifetime (RFC 9111). For a file last changed 300 days ago, that means roughly 30 days of tacit validity.
immutable: the file name is the real invalidation
For versioned files - those whose name contains a fingerprint of the content - the widespread recommendation is to keep them valid for a year and additionally mark them as unchanging: Cache-Control: max-age=31536000, immutable. For HTML, by contrast, no-cache is recommended so the browser asks back before every reuse (web.dev). This separation is sensible and widely adopted. But it has a condition that tends to get lost in day-to-day work: it only holds as long as the file name really changes with every change of content.
What immutable means is stated explicitly in the corresponding specification: the response will not change during its freshness lifetime, and the client should not issue a conditional request even when the user reloads the page (RFC 8246). That is precisely the purpose of the directive - and precisely why it is the least forgiving layer in the stack. If a file is served under the same name with new content, neither an ordinary nor a hard reload helps. The old build stays on the device until the lifetime expires, in the worst case for a year.
The same logic applies outside the browser. The rendering service behind Google Search keeps resources in its own store and does not follow the usual cache headers while doing so; the documentation therefore explicitly recommends giving file names a content fingerprint (Google Search Central). An outdated script or style file can keep having an effect there long after it has been replaced in the browser. How much consequence a single wrongly served component can have for visibility is shown by the article on a noindex tag accidentally shipped with a deploy.
Invalidating a file marked as unchanging does not run through headers, it runs through the file name. As long as the build process produces a new fingerprint on every change and the HTML points at the new name, the long lifetime is unproblematic. The moment a component slips past that chain - a hand-maintained file, an image replaced after the fact, a script from a different build step - the optimisation turns into invisible legacy. That chain therefore belongs in the build checks and in a post-deploy measurement from the outside.
The service worker that holds on to a dead file
A service worker is a script the browser places between the page and the network. It answers requests from its own storage and is therefore the only layer that can avoid the network entirely. For a long time this was a niche topic; in a 2025 survey, however, the share of websites with a service worker rose to 19.2 percent, and among the 10,000 most visited domains to 29.4 percent on desktop and 27.5 percent on mobile (HTTP Archive Web Almanac 2025). A considerable part of that growth comes from scripts delivered through tag management - that is, from a layer that operations frequently does not perceive as a cache at all.
The lifecycle is what matters for deploys. When the browser finds a changed service worker file, it installs it but then leaves it waiting until no page is controlled by the previous worker any more (W3C Service Workers). A single open tab is enough to keep the old build running. The instruction to skip that waiting state is widespread by now: in a 2025 analysis, 68 percent of desktop pages with a service worker and 63 percent of mobile ones used it (HTTP Archive Web Almanac). Operations still cannot rely on it in an individual case - where the instruction is missing, the wait lasts until the last tab is closed. At least the browser fetches the worker file past the cache on the next visit once the last check is more than 24 hours old, because longer freshness values do not take effect for that one script under the specification (W3C Service Workers).
The chosen runtime strategy matters too. In an analysis of the runtime strategies in use on desktop, 31.72 percent of them were the approach of asking the storage first, almost level with the network-first approach at 31.52 percent, followed by the combination of serving a stale copy and refreshing afterwards at 27.03 percent (HTTP Archive Web Almanac 2021). Asking the storage first serves the stored response without consulting the network at all - sensible for fonts, risky for content that changes with the deploy.
The new worker is ready but only takes over once all tabs running the old build are closed (W3C Service Workers). With visitors who keep the site permanently open like an application, that can take days. It goes unnoticed during checks, because a fresh test window receives the new build every time.
If the strategy of asking the storage first is applied to HTML or to files without a fingerprint, the stored copy wins against every change on the server. The page loads fast and shows the state of the last visit - a failure pattern that cannot be reproduced with a plain request without a browser.
Many workers build a list of files to keep on hand during installation. If that list points at file names that no longer exist after a deploy, the installation fails or the page tries to load components that have gone. The visible result is frequently a blank page with a formally correct status code.
A navigation fallback serves a stored shell when the network does not answer. If that shell is older than the deploy, it points at scripts from an earlier version. Such cases only surface when a check opens the page with a real browser and compares the visible state against the expectation.
HTML with a long lifetime: stale prices and legal texts
HTML is the entry point. It decides which file names are referenced, which prices are visible and which legal text applies. In the 2021 analysis, 23.4 percent of HTML responses counted as non-cacheable (HTTP Archive Web Almanac) - so the far larger share does end up in a cache. Where HTML is served with a lifetime of one day, a returning visitor sees the old page for up to 24 hours after the deploy without a single error being logged. The recommended setting for addresses without a fingerprint is therefore no-cache: storing is allowed, reuse only after asking back (web.dev). A relaunch sharpens this, because many addresses change at once - the article on monitoring a website migration sorts out that sequence.
Commercially and legally, stale pages are more than a cosmetic flaw. The German Price Indication Ordinance requires the total price to be stated correctly and completely (PAngV); an old price from the cache is an incorrect statement about the price and therefore open to challenge as a misleading commercial practice (UWG). Likewise, information about data processing has to match the actual state (Art. 13 GDPR) - a privacy notice that has been superseded since the last deploy but is still served from the cache fulfils that duty only on paper. The same applies to the legal notice, to withdrawal instructions and to shipping costs.
Pages with prices, availability, deadlines or legal texts should not be served with long freshness lifetimes, not even at the CDN. Where load time calls for caching, a short s-maxage value plus a targeted purge request after the deploy is the more robust route than a blanket long max-age for the browser: what sits at the CDN can be cleared, what sits on someone else's device as a rule cannot (RFC 9111). After every release, spot-check exactly those pages that carry figures and deadlines.
Clearing a cache is not the same as bypassing it
Two things are easily confused during troubleshooting. Clearing a cache means discarding stored responses so the next request is answered afresh. Bypassing a cache means routing your own check past it without touching its contents. For diagnosis the difference is decisive: whoever clears destroys the evidence. After a hard reload the page looks correct - but the question of what the next visitor receives has not been answered, only made unanswerable.
| Check | What it bypasses | What it does not reach | What it is good for |
|---|---|---|---|
| Reload the page | expired entries are fetched again | valid entries, service worker, CDN | a first look, little more |
| Hard reload | the browser cache of this tab | responses with immutable, CDN nodes, application cache | local narrowing down |
| Private window | profile, login and service worker | CDN and reverse proxy | separating out local state |
| Clear the site storage | browser cache and service worker storage | everything beyond your own device | circling the failure pattern |
| Request without a browser | the layers on your own device | the view of real visitors including scripts | diagnosing headers |
| Check points in several locations | the view of a single CDN node | the storage on the visitor's device | ongoing control after every deploy |
Two values in the response are worth a look for diagnosis. The Age header estimates the time in seconds that has passed since the response was generated or last validated at the origin server (RFC 9111); a value close to the configured lifetime points at a node about to refresh. And many intermediate layers set a status hint of their own showing whether the response came from storage. Together, those two separate a stuck CDN node from a wrongly built artefact more reliably than any amount of repeated reloading. It is also worth checking whether the page reports the expected status code at all - a response served as an error page with status 200 is a failure pattern of its own, described in the article on soft 404 pages and wrong status codes.
Checking after every deploy: an approach in seven steps
From the failure patterns described, a routine can be derived that works after every release without much effort. It starts from a simple idea: the page should state which version it is. Everything else is a comparison of that statement across all layers and locations.
- Ship a version marker: Write the build identifier into the HTML - as a meta entry in the head or as a response header of its own. That turns the soft question of whether the page looks right into the checkable question of whether the page contains version 42.
- Check with a real browser: A plain request fetches the HTML and judges headers, but it runs no scripts and knows no service worker. Only a check in a real browser shows what visitors actually see - that applies to cache state just as it does to the click paths stored in browser monitoring.
- Check from several locations: A CDN holds a separate copy per node. A single check point therefore only answers what things look like in that one place. Several locations make the difference between a widespread fault and a single stuck node visible.
- Compare the rendering: An image comparison before and after the deploy shows deviations that no status code reports - missing styles, old prices, sections that vanished. The article on detecting changes through image comparison describes the method.
- Keep an eye on response time: If a cache fails or is fully purged after a deploy, every request hits the origin server. Response time then rises sharply, often before visitors report anything - the article on server response time explains the connections.
- Check the service worker path separately: Verify that the worker file carries the expected state and that the list of precached files points at names that exist. Both can be set up as a check point of their own and repeated after every deploy.
- Alert on the missing version marker: If a page no longer contains the expected identifier, there is either an old cache state or an incomplete deploy. Both are a case for the team's notification channels, not for an accidental discovery during a customer call.
This routine dovetails with other checks that are due after a deploy anyway. Anyone who checks campaign parameters along the way notices early when a cached page has lost the reference to the new landing page - the groundwork is set out in the article on clean campaign tagging. And on multilingual sites a cache node can pin one language variant to the wrong address, which confuses the signals described in the article on hreflang errors.
Making cache traps permanently visible
Cache faults are unpleasant because they do not behave like faults. There is no outage, no red status code and no entry in the error log. There is only a page showing something other than intended - and the probability that somebody reports it depends on how prominent the affected spot is. A wrong price is noticed quickly; a superseded legal text in the footer frequently is not, for months.
That is why it pays to tie the check to the deploy rather than to chance. XICTRAQ fetches your pages with a real browser, runs the scripts and therefore sees the same build a visitor sees. The requests come from several locations, so a single stuck CDN node stands out instead of disappearing. The screenshot changelog records how the page looked before and after the release, and the image comparison shows deviations for which no status code exists. If the expected version marker fails to appear, availability and content monitoring reports that as a deviation - regardless of whether the origin server was switched over long ago.
The results build a history that can be captured in reports and status pages - useful for agencies that want to document the state of several client sites after a round of releases. Take a look at the check in the product demo or set up your first check point in the free plan and have the next deploy confirmed from the outside instead of merely assumed.
This article is based on data from HTTP Archive Web Almanac, RFC 9111, W3C Service Workers and web.dev. The figures cited refer to the state of the respective publication. Additional references are RFC 8246 on the immutable directive, RFC 5861 on stale-while-revalidate, the Google Search Central documentation on caching resources, and the requirements set out in the German Price Indication Ordinance (PAngV), the German Act Against Unfair Competition (UWG) and the GDPR. This information does not constitute legal or tax advice.
Why do visitors still see the old page after a deploy?
Why does a hard reload not help with some files?
immutable directive tell the client explicitly not to issue a conditional request even on reload (RFC 8246). The directive is meant for versioned files whose name changes with every content change. Applied to files whose name stays the same, the old build remains on the device until the lifetime expires. In practice, immutable appeared on 3.5 percent of responses (HTTP Archive Web Almanac 2021).How long can a service worker keep serving an old version?
What lifetime makes sense for HTML?
no-cache is recommended: the response may be stored, but it may only be reused after asking back (web.dev). For versioned files the opposite applies: a long lifetime plus immutable (RFC 8246). Pages with prices, deadlines or legal texts should as a rule have a short lifetime so a deploy reaches them promptly.How do I verify that the new version is really being served?
Age header is also useful, since it points at an old state in a shared cache (RFC 9111).