no-cache vs no-store vs max-age=0: What Each Directive Actually Does
This comparison clarifies the most misunderstood corner of HTTP Cache-Control Headers Explained, within Advanced Caching Strategies & CDN Architecture. The names suggest a ladder of strictness — "no cache", "no store", "zero age" — and many teams reach for the strongest-sounding option to be safe. That choice has real costs: no-store disables storage entirely, so every navigation, every back button press and every repeat visit downloads the full response again, and it can make pages ineligible for the back/forward cache.
In practice the directives answer different questions. no-store says "never write this response to any cache". no-cache says "you may store it, but must check with the server before each reuse". max-age=0 says "it is stale immediately" — which, with validators present, behaves almost like no-cache, and with must-revalidate is effectively the same. For most HTML that "must always be fresh", the right answer is revalidation, not refusal to store.
Rapid Diagnosis
- Inventory headers by content type.
curl -sIkey HTML, API and asset URLs; recordCache-Control,ETagandLast-Modified. - Look for
no-storeon public content. It is often set globally by frameworks or security middleware. - Check for validators.
no-cacheandmax-age=0only produce cheap 304 responses ifETagorLast-Modifiedis present. - Check repeat-visit transfer sizes. In DevTools, a reload should show small transfers (304) for revalidated resources; full sizes indicate
no-storeor missing validators.
Root Cause Analysis
1. Name-based assumptions. "no-cache" sounds like it prevents caching; it actually requires revalidation. Teams then add no-store to "really" prevent caching.
2. Security checklists applied globally. Guidance to use no-store for sensitive pages gets applied to every response.
3. Missing validators. Without ETag/Last-Modified, revalidation degrades to a full download, making no-cache look no better than no-store.
4. Confusion between browser and CDN behaviour. no-cache applies to all caches; teams wanting "CDN may cache, browser must revalidate" need s-maxage with max-age=0.
Step-by-Step: Choosing Correctly
1. Reserve no-store for genuinely sensitive responses
Account statements, payment details, health records, one-time tokens. Everything else is a candidate for a weaker directive.
2. Use no-cache (with validators) for "always fresh" HTML
Cache-Control: no-cache
ETag: "a1b2c3"
The browser stores the page and revalidates on each use; unchanged pages cost a header round trip, not a full download, and remain bfcache-eligible. (HTTP headers cannot carry comments — the trade-off: each navigation still pays one round trip to the server for revalidation.)
3. Separate browser and CDN policies with s-maxage
add_header Cache-Control "public, max-age=0, s-maxage=600, stale-while-revalidate=60";
# trade-off: browsers revalidate on every use while the CDN serves its copy for
# ten minutes. Content changes need a CDN purge to appear within that window.
Expected outcome: browsers always check freshness — against the nearby CDN, cheaply — and the origin is shielded.
4. Add validators everywhere you revalidate
Make sure the origin (or CDN) emits a stable ETag or Last-Modified for HTML and API responses, so revalidation returns 304s. ETag vs Last-Modified validators covers the details.
Verification
Reload pages with DevTools open (without "Disable cache"): HTML using no-cache should show 304s with tiny transfer sizes when unchanged; no-store responses will show full sizes. Run the bfcache test on pages you moved off no-store. In RUM, compare repeat-visit TTFB and transfer sizes before and after.
Worked Example: A Global no-store Removed
A web app's security middleware set Cache-Control: no-store on every response, including public marketing pages, CSS and JavaScript (served from the app server rather than a CDN). Repeat visits downloaded 1.1MB each time, LCP for returning visitors was barely better than for new ones, and back navigations always reloaded. The team scoped no-store to authenticated account routes, set no-cache with ETags on public HTML, and long-lived immutable caching on hashed assets. Returning-visitor LCP p75 improved by 900ms, repeat-visit transfer fell by 85%, and the bfcache restoration rate on public pages rose from near zero to over 70%.
Choosing Between no-cache and a Short max-age
no-cache guarantees a freshness check on every use, which costs a round trip even when nothing changed. A short max-age (say 60 seconds) lets the browser reuse the response without asking for that period, saving the round trip on quick repeat navigations at the cost of up to a minute of staleness. For HTML that changes rarely and where a minute of staleness is harmless — articles, documentation, product pages with CDN purging — max-age=60 plus stale-while-revalidate often gives a faster experience than no-cache. For HTML that reflects rapidly changing state, or where a stale page would be harmful (a ticket checkout), keep no-cache.
Common Mistakes
no-cache, no-storetogether.no-storedominates;no-cacheadds nothing.- Relying on
Pragma: no-cache. It is an HTTP/1.0 request header with no defined meaning on responses. no-cachewithout validators. Revalidation then downloads the full body every time.- Using
max-age=0and expecting CDNs to cache. Uses-maxagefor shared caches.
Edge Cases
must-revalidate. Prevents serving stale content when the origin is unreachable. Combine with stale-if-error deliberately if you want resilience.
private. Orthogonal to these three: it restricts storage to the browser. private, no-cache is a common, sensible policy for personalised HTML.
Service workers. They can implement their own caching regardless of headers; headers still govern the HTTP cache and CDNs.
Heuristic caching. Responses with no Cache-Control at all may be cached heuristically — see heuristic freshness when Cache-Control is missing.
FAQ
Is no-cache safe for sensitive pages?
It allows the response to be stored on disk, which may be unacceptable for highly sensitive data on shared devices. For those pages use no-store; for ordinary personalised pages, private, no-cache is usually appropriate.
Does max-age=0 behave exactly like no-cache?
Almost. Both require revalidation before reuse in normal operation. Without must-revalidate, caches may serve a max-age=0 response stale in some situations (for example, when the origin is unreachable); no-cache forbids reuse without successful validation.
Which directive do CDNs respect?
Most respect no-store and private by not caching, treat no-cache as "revalidate", and use s-maxage (or vendor headers such as CDN-Cache-Control) for their own TTLs. Check your CDN's documentation for HTML defaults.
Does no-store affect the back/forward cache?
Historically, yes — pages served with no-store were not stored in bfcache. See Cache-Control no-store and bfcache.
What should API responses use?
Public APIs: max-age=0 or short max-age for browsers with s-maxage for CDNs. Personal APIs: private, no-cache or no-store depending on sensitivity.
How do I change a global no-store safely?
Change it per route class rather than globally in one step. Start with static assets (which should be immutable anyway), then public HTML, and leave authenticated routes until the security review is done. Each step is independently verifiable with curl and DevTools, and each delivers its own improvement.
Do these directives apply to service worker caches?
No. The Cache Storage API used by service workers ignores HTTP caching directives; your service worker code decides what to store. Headers still govern the browser's HTTP cache, which the service worker's own fetches go through.
Related
- Cache-Control for HTML documents — complete HTML policies.
- Setting up immutable cache headers for hashed assets — the opposite end of the spectrum.
- Choosing SWR windows for HTML and APIs — adding staleness tolerance on purpose.