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:

  1. For the first 60 seconds after the response is cached, it is fresh. The edge serves it instantly with no origin contact.
  2. 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.
  3. 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:

AgeUser-visible behaviorWhat the edge does
0 ≤ age < max-ageHIT, no origin contactServe cached response
max-age ≤ age < max-age + SWRHIT with stale copyServe cached response + async fetch to origin
max-age ≤ age < max-age + SWR (second concurrent request)HIT with stale copyServe cached response; existing background fetch deduplicated
age ≥ max-age + SWRSynchronous revalidation, user waitsBlock; 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.

ConfigurationUpdate propagationUser latency on cache missOrigin RPS
max-age=60Up to 60 s200 ms (synchronous revalidation)One per POP per 60 s
max-age=3600Up to 60 min200 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:

  1. The response Age header — the number of seconds since the response was cached. Compare it to max-age to know whether you are in the fresh, stale, or expired window.
  2. The CDN's cache-status header (varies by vendor: cf-cache-status, x-cache, x-served-by). Look for values like REVALIDATED or STALE.
  3. 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

More From This Section