Stale-While-Revalidate Explained
Stale-while-revalidate is the single most useful HTTP cache directive most teams have never configured. It decouples freshness from latency: the cache can keep serving an older copy to users for milliseconds while it fetches a new copy from origin in the background, so nobody ever waits for a slow upstream fetch on a popular URL. Combined with stale-if-error — its sibling defined in the same RFC — it converts brittle origin-bound deployments into edge-resilient systems that survive minutes of origin downtime without users noticing.
The directive in one example
The smallest useful response header that uses SWR:
Cache-Control: public, max-age=60, stale-while-revalidate=600
Read this as three rules in sequence:
- For the first 60 seconds after the response is cached, it is fresh. The edge serves it instantly with no origin contact.
- From 60 to 660 seconds (60 + 600), the response is stale-but-usable. The edge serves the stale copy immediately to the user and kicks off a background fetch to origin to get a fresh copy.
- After 660 seconds, the response is expired. The edge cannot serve it; the next request blocks on an origin fetch.
The first user after second 60 still gets the stale response in milliseconds. The fresh copy that arrives a few hundred milliseconds later is stored and served to every subsequent user until its own max-age expires.
What problem does this actually solve
Without SWR, a cache has two options when the freshness window ends:
- Block the user on a synchronous revalidation. The user pays the full origin RTT. On a slow or distant origin this can be hundreds of milliseconds, every time the cache expires.
- Extend max-age. Cheaper for users but stretches the time between origin updates and cache reflection. Bad for content that genuinely should update on a fast cadence.
SWR is the third option: short max-age (so updates propagate quickly) plus a long stale window (so no user ever pays the revalidation cost on the hot path).
How edges implement the background fetch
The exact mechanics differ per CDN but the high-level state machine is universal:
| Age | User-visible behavior | What the edge does |
|---|---|---|
| 0 ≤ age < max-age | HIT, no origin contact | Serve cached response |
| max-age ≤ age < max-age + SWR | HIT with stale copy | Serve cached response + async fetch to origin |
| max-age ≤ age < max-age + SWR (second concurrent request) | HIT with stale copy | Serve cached response; existing background fetch deduplicated |
| age ≥ max-age + SWR | Synchronous revalidation, user waits | Block; fetch from origin; serve fresh response |
The deduplication in row three is important — without it, every concurrent stale request would trigger its own origin fetch, defeating the purpose. Every mature CDN implementation collapses concurrent background revalidations for the same cache key into a single upstream request.
Stale-if-error: the resilience sibling
Defined in the same RFC 5861, stale-if-error covers the case when origin is broken instead of slow:
Cache-Control: public, max-age=60, stale-while-revalidate=600, stale-if-error=86400
This adds a fourth rule: if the edge attempts to fetch a fresh copy and origin returns 5xx, times out, or is unreachable, the edge may keep serving the stale cached copy for up to 86,400 seconds (24 hours) past max-age. That converts an origin outage from a user-visible 5xx into invisible stale-content serving.
For high-traffic public content this is the most effective single line of cache configuration you can deploy. A 1-hour origin outage that would otherwise produce hours of error pages becomes a 1-hour window where some content is up to a day stale — almost always preferable.
SWR vs max-age extension: a worked example
Imagine a popular product page that receives 10,000 requests/second across the edge network. Origin RTT is 200 ms.
| Configuration | Update propagation | User latency on cache miss | Origin RPS |
|---|---|---|---|
max-age=60 | Up to 60 s | 200 ms (synchronous revalidation) | One per POP per 60 s |
max-age=3600 | Up to 60 min | 200 ms (rarely paid) | One per POP per 3600 s |
max-age=60, stale-while-revalidate=3540 | ~60 s (background) | < 5 ms (always served from cache) | One per POP per 60 s |
The third row is the only configuration that delivers both fast updates and fast user responses. The first row trades user latency for freshness; the second row trades freshness for latency; SWR removes the trade.
Common configuration patterns
Reasonable defaults for different content categories:
- Marketing pages.
max-age=300, stale-while-revalidate=86400, stale-if-error=86400— 5 min fresh, day-long stale window, day-long error fallback. - Article / blog posts.
max-age=60, stale-while-revalidate=86400— react to edits within a minute, never block users. - Product catalog.
max-age=30, stale-while-revalidate=300— short fresh window for price/availability accuracy, brief stale window to absorb traffic spikes. - Static assets (versioned filenames).
max-age=31536000, immutable— SWR is unnecessary because the URL changes when content changes; no revalidation ever needed. - API JSON for public data.
max-age=10, stale-while-revalidate=60, stale-if-error=600— small window, but the per-edge origin load is bounded.
When stale-while-revalidate is wrong
SWR assumes the user is willing to see slightly old data. That assumption is correct for most content. It is wrong for:
- Authentication and authorization. Stale tokens, stale ACL responses. Don't cache; certainly don't serve stale.
- Account balances and transactional state. Showing a 60-second-old balance after a purchase looks like the purchase failed.
- Inventory at checkout. Stale "in stock" can lead to oversold orders.
- Security configuration responses. Stale CSP, CORS, or feature-policy headers can break or weaken security posture.
For these, mark responses Cache-Control: no-store or private, no-cache and don't go near SWR. The point of SWR is to make cacheable content fast; non-cacheable content needs different mechanisms entirely.
How SWR interacts with origin updates
One subtle behavior: SWR does not magically know that origin has updated. The background fetch only fires after a user requests the URL during the stale window. On low-traffic URLs that means content can stay stale for the full max-age + SWR window before anyone triggers a refresh. For these, either explicit purges (see purge and invalidation) or shorter max-age values are the right tool.
On high-traffic URLs the opposite is true: the first request after max-age expires triggers a refresh, all concurrent requests are served the stale copy plus that single background fetch, and within hundreds of milliseconds the cache is updated. Traffic volume itself drives freshness on busy URLs.
SWR and the browser cache
The HTTP-level SWR directive is honored by browser caches too — not just by CDNs. When a browser revisits a page whose response carried stale-while-revalidate, it returns the cached copy from disk for the fast first paint and revalidates in the background. The next navigation has the fresh copy already in cache. This makes SWR a perceived-performance optimization for repeat visitors even before considering the CDN layer.
Note that the JavaScript "SWR" library (used in React data fetching) implements the same pattern at the application layer. It is conceptually identical but operates on JSON responses inside the page, not on HTTP cache entries.
Debugging SWR behavior
Whether SWR is actually firing is easy to verify but easy to misinterpret. Inspect:
- The response
Ageheader — the number of seconds since the response was cached. Compare it tomax-ageto know whether you are in the fresh, stale, or expired window. - The CDN's cache-status header (varies by vendor:
cf-cache-status,x-cache,x-served-by). Look for values like REVALIDATED or STALE. - Whether origin logs show the expected one-request-per-edge-per-window pattern. If origin sees a request per user request, deduplication is misconfigured.
For a deeper view, see CDN logs and observability.
Frequently Asked Questions
What does stale-while-revalidate do?
Stale-while-revalidate tells caches that once a response has passed its max-age, they may keep serving the stale copy for a configurable additional window while asynchronously fetching a fresh copy from the origin in the background. The user gets a fast response from cache; the next request after the background refresh completes gets the updated content. Defined in RFC 5861.
What is the difference between stale-while-revalidate and stale-if-error?
Stale-while-revalidate allows serving stale content during a normal background refresh. Stale-if-error allows serving stale content when the origin returns an error (5xx) or is unreachable. They are independent directives and are commonly used together: SWR for performance, stale-if-error for resilience.
How is stale-while-revalidate different from a normal max-age extension?
Extending max-age makes the response fresh for longer, which means users see older content for longer and the cache cannot detect that origin has changed it. Stale-while-revalidate keeps max-age short for freshness detection but allows the cache to serve the stale copy briefly while it fetches the new one — so users get fast responses without sacrificing freshness.
Do browsers honor stale-while-revalidate?
Modern Chromium-based browsers and Firefox honor stale-while-revalidate in their HTTP cache. Safari has partial support. CDN edge support is nearly universal — Cloudflare, Fastly, CloudFront, Akamai, and most others implement it. For client-side data fetching, the term "SWR" is also used by the React hooks library, which implements the same pattern in JavaScript rather than relying on the HTTP header.
When should I not use stale-while-revalidate?
Avoid it for responses where stale data would be incorrect or dangerous: account balances, prices at checkout, security tokens, real-time inventory at the point of purchase. Use it freely for content where a few seconds of staleness is irrelevant: marketing pages, product listings, articles, blog posts, public APIs that aggregate slow-changing data.
Related Guides
Cache-Control Headers
The full directive set behind every CDN's caching behavior.
ETag and Conditional Requests
How caches revalidate without re-downloading the response body.
Purge and Invalidation
Forcing fresh fetches when natural expiry isn't fast enough.
Cache Hit Ratio
Where SWR fits in the hit-rate measurement model.
More From This Section
All CDN & Edge Guides
How CDNs work, cache headers, anycast, edge functions, and security.
Anycast vs GeoDNS
Anycast and GeoDNS compared — how each routes users to CDN points of presence, BGP convergence, GeoDNS resolver…
Cache Hit Ratio Explained
What cache hit ratio actually measures, the difference between request and byte hit rate, and the configuration changes…
Run a Speed Test
Measure download, upload, ping, and jitter in your browser.