Web caching explained: the fastest request is the one you never make
Web caching stores a copy of a file closer to the user — in their own browser, or at a CDN’s edge server — so that a repeat request is served instantly instead of making the full round trip to your origin, which costs 100 to 300 milliseconds just to connect and receive the first byte. The fastest request is the one the browser never has to make. The entire discipline reduces to one question: does the URL change when the content changes? If it does — a fingerprinted file like app.a1b2c3d4.js — you cache it for a year with immutable and the browser never asks about it again. If it doesn’t — an HTML page — you serve it no-cache so the browser revalidates and gets a tiny 304 Not Modified when nothing moved. Add s-maxage to tune the CDN separately, stale-while-revalidate for updates that never make a user wait, and content-hash filenames so a new deploy busts the cache automatically. Get that split right and repeat visits and traffic spikes become near-free — and a static site, which is almost nothing but cacheable immutable files, gets the whole benefit by default.
What actually happens when a browser caches a file?
Caching stores copies of resources — pages, images, scripts — closer to the user, either in their browser or in an intermediate cache like a proxy or CDN, so a cached copy can be served without hitting the origin server (DebugBear, 2026). The result is faster responses and lower server load, because the work of fetching, and often of generating, the resource simply doesn’t happen a second time.
The size of the win is easy to underrate. As one guide puts it, the most performant request is the one not made, and a memory-cache hit in the browser is effectively instant compared with the 100 to 300 milliseconds you’d otherwise wait just to complete a handshake and see the first byte (Jono Alderson, 2026; DebugBear, 2026). That saved round trip is time a returning visitor never spends staring at a blank screen, which is why caching sits directly upstream of the loading metrics in our pillar on Core Web Vitals and site speed.
The one question that decides your whole strategy
Every good caching decision comes down to a single question: does the URL change when the content changes? If it does — because the filename carries a content hash — you can cache aggressively with a long max-age and immutable. If it doesn’t — an HTML document or an API response served from a stable URL — you use shorter windows, revalidation, and stale-while-revalidate to balance freshness against speed (Benedikt Sperl, 2026).
That framing prevents the two failures that dominate real sites: caching a changeable resource so hard that users are stuck with a stale version, or caching nothing at all because the defaults felt safer (Benedikt Sperl, 2026). Answer the URL question first, and the specific directives below almost pick themselves.
The Cache-Control directives that matter
Cache-Control is the primary header for caching, having replaced the older Expires, and it takes comma-separated directives (DebugBear, 2026). The freshness ones come first: max-age=seconds sets how long a response stays fresh in both browsers and shared caches, while s-maxage applies only to shared caches like CDNs and overrides max-age there — letting you keep a short window in the browser and a longer one at the CDN, a directive many developers don’t realise exists (Jono Alderson, 2026).
The rest set the rules of storage. public lets any cache store the response and private restricts it to the user’s own browser; no-cache means store it but revalidate before each use, while no-store means don’t cache at all — the two are opposites, not synonyms (DebugBear, 2026). immutable tells a browser the file will never change so it shouldn’t revalidate even on reload, and stale-while-revalidate — the most underused directive in the toolkit — lets a cache serve a slightly stale response instantly while it fetches a fresh one in the background, so the user never waits on the origin (DigitalApplied, 2026).
The two recipes you’ll use most
For versioned static assets — CSS, JavaScript and fonts whose filenames include a content hash — the recipe is Cache-Control: public, max-age=31536000, immutable, caching them for a year with no revalidation (Benedikt Sperl, 2026). This is safe precisely because the hash guarantees the URL changes whenever the content does, which is exactly why frameworks apply this header to everything under their hashed-asset directories (DigitalApplied, 2026).
For HTML documents, do the opposite: use no-cache (or the equivalent max-age=0, must-revalidate) so the browser always checks with the server, picks up new hashed asset URLs after a deploy, and still benefits from a fast 304 Not Modified when the page hasn’t changed (Benedikt Sperl, 2026). Those two recipes — cache assets forever, revalidate HTML every time — cover the large majority of a normal site.
How cache-busting works
Long caching creates one obvious problem: if a file is cached for a year, how does anyone ever get an update? The answer is content-based hashing — include a hash of the file’s contents in its filename, so app.js becomes app.a1b2c3d4.js (Benedikt Sperl, 2026). When the content changes, the hash changes, the filename changes, and the browser treats it as an entirely new resource to download, while the old cached copy simply stops being referenced.
Modern build tools like Vite, Webpack and Rollup generate these hashed filenames automatically, so cache-busting is usually a default rather than a task (getpagespeed, 2026). Two practical notes: prefer a filename hash over a query string like ?v=123, because some CDNs ignore query strings, and avoid bundling your whole site into one file, since a single change then busts the entire bundle for every returning visitor (getpagespeed, 2026; DebugBear, 2026).
How a browser checks without re-downloading
When a resource is set to revalidate, the browser doesn’t blindly re-download it — it asks whether anything changed, using validators. An ETag is a fingerprint of the response and Last-Modified is its timestamp; the browser sends them back as If-None-Match and If-Modified-Since, and if nothing has changed the server replies 304 Not Modified with no body at all (oneuptime, 2026). That exchange is why no-cache isn’t slow: an unchanged HTML page costs a tiny header round trip rather than a full re-download, which is the whole point of revalidation.
Browser and CDN: two caches, one set of rules
Caching happens on at least two layers that read the same headers but sit in different places: the browser cache on the user’s device, and the CDN cache on distributed edge servers (RequestMetrics, 2026). A CDN without a real caching strategy is just an expensive proxy — the difference between a tuned one and a careless one can be 50-millisecond responses versus 500, and an origin that coasts versus one that gets hammered (oneuptime, 2026). This is where s-maxage and tag-based purging earn their place, letting you cache HTML at the edge for a long time and purge it the instant it’s published or edited, covered further in our guide on what a CDN is.
One subtlety trips up even experienced teams: max-age measures elapsed time since the response was generated on the origin, not since a CDN received it, and the Age header deducts that transit time — so a response with max-age=600 that spent two minutes reaching the edge is already two minutes into its life on arrival (DigitalApplied, 2026). Verify real behaviour with curl -I and your CDN’s cache-hit rate rather than assuming the headers do what you intended.
The mistakes that serve stale or leaked content
A few caching errors are common and costly. Putting immutable on an HTML page, or on any resource that can change under the same URL, locks visitors into stale content for months (Jono Alderson, 2026). Allowing an authenticated response into a shared cache is worse — if a CDN caches a page built for a logged-in user, it can serve one person’s data to another, which is why private routes must return private, no-store (oneuptime, 2026).
The subtler class of bug comes from layered caches disagreeing. Most stale-content problems trace back to two layers both thinking they own the same value, so the discipline is to decide, for each piece of data, which layer owns its freshness — hashed assets belong to the browser at a one-year TTL, per-user data belongs behind private, no-store, and a genuinely live value belongs nowhere durable and should be served fresh (DigitalApplied, 2026). And avoid Vary: User-Agent, which multiplies stored variants and shreds your hit rate (DigitalApplied, 2026).
Why a static site is a caching dream
Everything above is easier when the site is static, because a static site is almost nothing but cacheable files. Its HTML, its content-hashed CSS and JavaScript, and its self-hosted fonts are all safe to cache aggressively at the edge, and there’s no per-request database query or per-user personalisation forcing a no-store on the important stuff. The two recipes cover nearly the whole site: hashed assets for a year with immutable, HTML revalidated on each request.
This is a structural advantage, not a tuning trick. A build tool like Astro fingerprints assets with content hashes by default, so the aggressive one-year caching recipe applies out of the box rather than being something a developer has to engineer and maintain (DigitalApplied, 2026). It compounds with the rest of the stack: the self-hosted fonts you cache forever, the third-party scripts you never loaded, and the CDN that serves it all from the edge are the same decision seen from different angles — build the site so the fastest request, the one never made, is the common case rather than the exception.
Frequently asked
- What is web caching in simple terms?
- Web caching stores a copy of a file — an HTML page, an image, a script — somewhere closer to the user than your origin server, either in the user's own browser or in a shared cache like a CDN. When the file is requested again, the cached copy is served without contacting the origin, which makes it faster and reduces server load. The saving is real: a memory-cache hit is effectively instant, compared with the 100 to 300 milliseconds it takes just to complete a connection and receive the first byte from a server.
- What's the difference between no-cache and no-store?
- They sound similar but do opposite things. no-cache means the response can be cached, but the browser must revalidate it with the server before using it each time — so you still get a fast 304 Not Modified response when nothing has changed. no-store means do not cache the response at all, on any device or intermediary; it's for sensitive or per-user data that must never be reused. Use no-cache for HTML you want kept fresh; use no-store only for authenticated or private responses.
- When should I use the immutable directive?
- Only on a URL whose content will never change — which in practice means a file with a content hash in its name, like app.a1b2c3d4.js. For those, Cache-Control: public, max-age=31536000, immutable caches the file for a year and tells the browser not to revalidate even on reload. Never put immutable on an HTML page or any resource that can change under the same URL, because you'll lock visitors into a stale version for months with no way to push an update short of changing the URL.
- How does cache-busting work?
- Cache-busting solves the problem of updating a file you've told browsers to cache for a year. The technique is to include a hash of the file's contents in its filename, so app.js becomes app.a1b2c3d4.js. When the content changes, the hash changes, the filename changes, and the browser treats it as an entirely new resource it must download — while the old cached copy simply becomes irrelevant. Modern build tools like Vite and Webpack do this automatically, and filename hashes are more reliable than query strings, which some CDNs ignore.
- Does caching help if my site is static?
- Enormously — a static site is almost nothing but cacheable files. Its HTML, its hashed CSS and JavaScript, and its self-hosted fonts are all safe to cache aggressively at a CDN's edge, with none of the per-request database work or per-user personalisation that forces a dynamic site to disable caching. Build tools like Astro fingerprint assets with content hashes by default, so the aggressive one-year immutable caching recipe applies out of the box rather than being something you have to engineer.