Skip to content
GDPR compliant. Hosted in DE.
14-day free trial →
Cache layersDeploy verificationImage comparison
Performance 13 min read 30 Aug 2026

Stale Cache After Deploy: Finding Outdated Pages

The new version is live, visitors still see the old one. How cache layers serve stale pages and how to check after every deploy what actually reaches people.

X

XICTRAQ Team

XICTRAQ

PerformanceCachingDeploymentMonitoring
62.2 %
of desktop responses carry a max-age directive (HTTP Archive Web Almanac 2021)
24 h
before browsers re-check the service worker script at the latest (W3C Service Workers)
10 %
of the time since last modification as heuristic freshness without headers (RFC 9111)
Five cache layers between deploy and visitorWhich layer serves which version once release 42 goes live (lifetimes per RFC 9111 and web.dev)Cache layer on the way to the visitorserved after the deploylifetime and who clears it1Browser cachekeeps files locally until max-age expiresVersion 41 (old)max-ageonly the visitor clears it2Service workeranswers requests from its own storageVersion 41 (old)activationwaits until all tabs are closed3CDN nodeevery location holds its own copyVersion 41 (old)s-maxagea purge request clears it4Reverse proxyserves pre-rendered pagesVersion 42 (new)restartoperations clears it on deploy5Application cacheholds fragments and query resultsVersion 42 (new)invalidationapplication code clears itPost-deploy checkpoint: does every layer really carry version 42?a real browser instead of a plain request, several locations, alert on a missing version marker3.5 %of responses use immutable (Web Almanac)max-age appears on 62.2 % of desktop responses, immutable on 3.5 % - a deploy has to reach every layer separately (Web Almanac)
The deploy is through, the pipeline reports success, the origin server answers with the new build. And yet a customer calls in the afternoon: the old price is still on the page, the new offer is missing, and nothing has changed for days. Stale cache after a deploy ranks among the more unpleasant failure patterns in web operations, because it slips past the usual checks. On your own machine everything is correct, the status code is 200, and a quick look at the homepage seems clean. Between the origin server and the visitor's screen, however, sit up to five storage layers, each with its own idea of how long a response stays valid. This article sorts the layers, walks through the typical traps from 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.

A cache hit is not a fault - the wrong cache hit is

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.

LayerWhat it storesWho clears itTypical lifetime
Browser cachefiles and sometimes HTML on the visitor's deviceonly the visitoruntil max-age expires
Service workerits own copy in the site's storagethe worker lifecycleuntil the new build activates
CDN noderesponses held separately per locationa purge request or s-maxagedifferent per node
Reverse proxyfully rendered pages in front of the applicationoperations during the deployusually minutes to hours
Application cachefragments, queries, rendered building blocksthe application codeuntil 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.

A typo in the directive fails silently

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.

New content needs a new name

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 waiting build

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.

Storage first, network second

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.

The manifest points at deleted files

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.

The fallback to old HTML

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.

Prices and legal texts belong on a short lifetime

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.

CheckWhat it bypassesWhat it does not reachWhat it is good for
Reload the pageexpired entries are fetched againvalid entries, service worker, CDNa first look, little more
Hard reloadthe browser cache of this tabresponses with immutable, CDN nodes, application cachelocal narrowing down
Private windowprofile, login and service workerCDN and reverse proxyseparating out local state
Clear the site storagebrowser cache and service worker storageeverything beyond your own devicecircling the failure pattern
Request without a browserthe layers on your own devicethe view of real visitors including scriptsdiagnosing headers
Check points in several locationsthe view of a single CDN nodethe storage on the visitor's deviceongoing 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.

Sources and Studies

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?
Because several storage layers typically sit between the origin server and the screen: browser cache, service worker, CDN node, reverse proxy and a cache inside the application itself. Each holds a response for as long as its freshness value allows. The HTTP caching standard provides no way for a server to remove a stored response from someone else's cache (RFC 9111). A deploy therefore reaches only the origin server at first; every further layer refreshes on its own rhythm.
Why does a hard reload not help with some files?
Responses carrying the 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?
A newly installed service worker waits until no page is controlled by the previous worker any more (W3C Service Workers). If a tab stays open permanently, that state can persist for a long time. 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 script under the specification (W3C Service Workers). Experience shows the state frequently only resolves once visitors close every tab of the site.
What lifetime makes sense for HTML?
For addresses without a fingerprint in the name - that is, for ordinary 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?
The most robust approach is a version marker in the page itself, for instance the build identifier as a meta entry or as a response header. An automated check point then opens the page with a real browser from several locations and reports when the expected identifier is missing. An image comparison against the state before the deploy helps as well, because it shows deviations that no status code reports. The Age header is also useful, since it points at an old state in a shared cache (RFC 9111).
Is a stale price in the cache a legal risk?
It can be. The German Price Indication Ordinance requires a correct and complete statement of the total price (PAngV), and incorrect statements about price are open to challenge as a misleading commercial practice (UWG). Something similar applies to superseded mandatory information about data processing (Art. 13 GDPR). A short lifetime for price-carrying pages plus a spot check after every deploy typically lowers that risk considerably. This note does not replace legal advice.

Related in XICTRAQ

Start in 60 seconds

Let XICTRAQ watch your website.

Permanent free plan available. No credit card, no installation, no hidden fees.