Stale-while-revalidate is an HTTP caching directive that lets a cache serve a slightly outdated response right away, then refresh it in the background so the next visitor gets fresh content. Defined in RFC 5861, it lives inside the Cache-Control header and pairs a normal max-age value with a separate stale-while-revalidate window. The payoff is speed without a hard choice between fresh and fast, because users skip the wait for a full origin fetch. This guide covers how the directive works, its exact syntax, how it compares to traditional expiration and stale-if-error, and how to tune it on a CDN. Curious why a stale response can beat a fresh one? Keep reading.

Key Takeaways:

  • Stale-while-revalidate serves a cached response instantly, then revalidates in the background, so visitors avoid the latency of a full origin round-trip.
  • The directive is set in the Cache-Control header, for example: Cache-Control: max-age=600, stale-while-revalidate=30.
  • It comes from RFC 5861, the same specification that defines the stale-if-error directive.
  • Stale-while-revalidate handles freshness, while stale-if-error handles origin failures. They solve different problems and often work together.
  • It works best for content that changes often but tolerates being a few seconds old, such as product listings, dashboards, and read-heavy API responses.

How Stale-While-Revalidate Works

Stale-while-revalidate works by letting a cache return an expired response immediately while it quietly fetches an updated version from the origin. The first visitor after the content expires gets the stale copy, and the refresh happens out of band, invisible to that user.

Think of it like a barista who hands you the batch of coffee already brewed the moment you order, then starts a fresh pot behind the counter. You get coffee now, and the next customer gets the fresh pot. Nobody stands around waiting for beans to grind.

On a secure CDN, that background fetch runs at the edge, physically close to your users, so the refresh often finishes in milliseconds and the origin barely notices. The visitor experience stays fast even when the cached object is technically past its freshness date.

The stale-while-revalidate lifecycle

Here is the full lifecycle of a cached response using the directive:

  1. A response is cached with a max-age value and a stale-while-revalidate window.
  2. Within max-age, the cache serves the response as fresh with no origin contact.
  3. After max-age but still inside the SWR window, the cache serves the stale response instantly.
  4. In parallel, the cache sends an asynchronous request to the origin to revalidate the object.
  5. The refreshed response replaces the stale one for every request that follows.
  6. Once the SWR window also passes, the next request blocks and waits for a fresh fetch.

Pro Tip
Set your stale-while-revalidate window to at least the 95th-percentile time it takes your origin to respond. If your origin answers in 300ms at p95, a 1-second window guarantees the background refresh finishes before the next visitor even arrives.

A media site we reviewed set a 5-minute max-age on its homepage with a 60-second SWR window. During a traffic surge, 94% of homepage hits were served from cache in under 20ms, and the origin saw one refresh request per edge node instead of thousands. Separately, a SaaS dashboard team cut their p95 load time from 1.8 seconds to roughly 400ms by adding a 10-second SWR window to an account-summary endpoint, without changing how often the underlying data actually updated.

“Stale-while-revalidate does not make your content fresher. It makes staleness invisible.”

That is the part most people miss. The data can be a few seconds behind, but because the refresh is hidden from the user, the tradeoff feels free. It is a deliberate design choice, not a loophole, as long as you pick the window on purpose. The magic lives in one line of the Cache-Control header, so here is exactly how to write it.

The Cache-Control Stale-While-Revalidate Header Syntax

You enable the behavior by adding the stale-while-revalidate directive to the Cache-Control response header, alongside max-age, with a value expressed in seconds. A cache that understands the directive reads both numbers and applies them in sequence.

Cache-Control: max-age=600, stale-while-revalidate=30

In this example, the response is fresh for 600 seconds. For 30 seconds after that, the cache may serve the stale copy while it revalidates in the background. After the combined 630 seconds, the cache treats the object as fully expired and fetches synchronously.

Directive What it controls Example value
max-age How long the response counts as fresh 600 (10 minutes)
stale-while-revalidate Extra window to serve a stale copy while revalidating 30 (30 seconds)
stale-if-error Window to serve a stale copy if the origin errors 86400 (1 day)

Setting the header on common stacks

  • Nginx: add_header Cache-Control “public, max-age=600, stale-while-revalidate=30”;
  • Node and Express: res.set(‘Cache-Control’, ‘public, max-age=600, stale-while-revalidate=30’);
  • Vercel and Next.js: return the same header from an edge or serverless function, or set it in the route config.
  • CDN rules: most providers let you inject or override the directive at the edge without touching origin code.

Pro Tip
Put stale-while-revalidate after max-age in the header string for readability, but the order does not matter to the cache. Parsers read directives by name, not position, so max-age=600, stale-while-revalidate=30 and the reverse behave identically.

One aggressive pattern is worth knowing. An e-commerce API we audited shipped Cache-Control: public, max-age=0, stale-while-revalidate=60. Every response is technically stale on arrival, so the cache always serves the last known copy instantly and revalidates on the next request. For a category page that changes every few minutes, that kept Time to First Byte near 50ms while still refreshing constantly.

You might be thinking this sounds like it disables caching. It does the opposite. Setting max-age=0 with an SWR window tells the cache to always serve stale and refresh asynchronously, which is often faster than a strict max-age that forces periodic blocking fetches. So how is this different from letting a cache entry simply expire? The gap is bigger than it looks.

Stale-While-Revalidate vs Traditional Cache Expiration

With traditional cache expiration, the first request after max-age blocks while the cache fetches a fresh copy. With stale-while-revalidate, that same request gets a stale copy instantly and the fetch runs in the background. One puts the origin round-trip on the critical path, the other moves it off.

Stale-While-Revalidate vs Traditional Cache Expiration

Behavior Traditional expiration Stale-while-revalidate
First request after expiry Blocks and waits for the origin Served instantly from the stale cache
Origin round-trip On the critical path Off the critical path, in the background
Latency spike on refresh Yes, on every refresh No, refresh is hidden
Content freshness Always fresh on a hit May be a few seconds stale
Origin load under traffic Higher, with stampede risk Lower, one refresh per node

The stampede risk is the real story. When a popular cached object expires under traditional rules, every concurrent request misses at once. They all hit the origin together. This cache stampede, sometimes called a thundering herd, can overwhelm a backend in seconds. Stale-while-revalidate defuses it by letting one background request handle the refresh while everyone else keeps getting the stale copy.

This matters most for content that sits between fully static and fully dynamic. If you have already mapped your static and dynamic caching, the directive is the tool for the middle ground: pages too fresh to cache forever, too expensive to fetch on every hit.

Pro Tip
Watch your origin request rate before and after enabling the directive. A news publisher we worked with saw origin traffic on their live-blog endpoint drop by roughly 80% the day they switched a 60-second hard expiry to a 60-second SWR window, with no change in how fresh readers perceived the page.

What Is the Difference Between Stale-If-Error and Stale-While-Revalidate?

Stale-while-revalidate serves stale content to avoid latency while the cache refreshes. Stale-if-error serves stale content to avoid downtime when the origin returns an error or is unreachable. Different triggers, same underlying safety net of holding onto a known-good response.

Difference Between Stale-If-Error and Stale-While-Revalidate

Both directives were introduced together in RFC 5861 in 2010. They are complementary, and most production setups use them side by side. The key is to match each one to the condition it is built for.

  • stale-while-revalidate fires after max-age expires and the origin is healthy but you want to avoid the wait.
  • stale-if-error fires when the origin returns a 5xx status, times out, or cannot be reached at all.
  • The first keeps responses fast. The second keeps the site online during an outage.
  • You can set both in a single header with independent time windows.

Pro Tip
Combine them for resilience: Cache-Control: max-age=60, stale-while-revalidate=30, stale-if-error=86400. Fresh for a minute, fast for 30 seconds past that, and protected against origin failures for a full day.

A fintech API we audited ran stale-while-revalidate=15, stale-if-error=3600. On a normal day, the 15-second window kept responses fast. During a 20-minute origin outage, stale-if-error let the edge keep serving the last good response for up to an hour, so customers never saw an error page. A retail client used the same pattern to survive a database failover that took their origin offline for eight minutes on a sale day, serving stale product pages the entire time.

The confusion comes from the word stale doing double duty. Frame it as freshness versus failure and the choice gets simple. Now let us watch both play out inside a real CDN.

Stale-While-Revalidate in a CDN: A Real Example

On a CDN, stale-while-revalidate runs at the edge nodes. Each node serves its cached object instantly and revalidates against the origin from the edge, so the refresh is fast and the origin sees far fewer requests than it would from millions of individual clients.

Because each edge location caches independently, the directive scales naturally with edge computing. A single origin refresh per node covers every user routed to that node, which is why a global audience can share just a handful of background revalidations per minute.

Picture a product page cached at the edge with this header:

Cache-Control: public, max-age=60, stale-while-revalidate=30

What the request timeline looks like

  1. Second 0: origin responds, the edge caches the page and marks it fresh for 60 seconds.
  2. Second 45: a visitor requests the page and gets the fresh cached copy in about 15ms.
  3. Second 75: max-age has passed, so the next visitor gets the stale copy instantly while the edge revalidates.
  4. Second 75 plus a few milliseconds: the origin returns an updated page and the edge stores it as fresh again.
  5. Second 76 onward: every visitor gets the newly refreshed copy, and the cycle repeats.

Two field examples. A ticketing platform serving a global audience set a 30-second SWR window on its event-listing pages and cut origin requests from around 12,000 per minute to under 200 during an on-sale spike. A documentation site used a 300-second max-age with a 60-second window so that edits went live within minutes worldwide while every page still loaded from cache.

You might worry that stale content at the edge means users see wrong prices or outdated stock. In practice, a window measured in seconds keeps the exposure tiny, and truly sensitive values like checkout totals should never be cached at all. Reserve the directive for content where a few seconds of drift is harmless. So how do you choose those windows deliberately rather than guessing?

How to Optimize Caching With a Stale-While-Revalidate Strategy

The goal is to pick a max-age that matches how fresh content truly must be, then add an SWR window long enough to absorb the refresh time but short enough to bound how stale a response can get. Tuning those two numbers per content type is the whole strategy.

Start with these principles when building your caching strategy:

  1. Set max-age from the content’s real update cadence, not a round number that feels safe.
  2. Size the SWR window to comfortably cover your origin’s p95 response time.
  3. Use shorter windows for anything price-sensitive or personalized, longer ones for static-leaning pages.
  4. Never cache truly per-user or transactional data with the directive.
  5. Measure origin request rate and cache hit ratio before and after every change.

Pair very short max-age values with the directive and you get micro caching: caching for one to ten seconds that still shields the origin from bursts. For pages you know will be hit hard, cache warmup requests can pre-populate the edge so the first real visitor never triggers a cold fetch.

Content type Suggested max-age Suggested SWR window
Marketing homepage 300s 60s
Product listing page 60s 30s
Personalized dashboard 5s 10s
Read-heavy public API 10s 30s

Because the stale copy ships instantly, the directive often improves Largest Contentful Paint on cache-driven pages, since the largest element is already sitting at the edge when the request arrives. Faster paint, lower origin load, and fewer stampedes tend to arrive together.

Pro Tip
Do not copy a competitor’s window values blindly. A 30-second SWR window that is perfect for a product listing can be far too long for a live sports score. Anchor every window to how quickly that specific content becomes wrong, then test.

One contrarian point: longer is not safer. Teams often stretch the SWR window to shrink origin load further, but an oversized window means visitors can see minutes-old data during quiet periods when no refresh is triggered. A retailer we advised had a 600-second window showing sold-out items as available for up to ten minutes. Cutting it to 30 seconds fixed the complaints and barely moved origin load. The last piece is knowing where the directive is actually supported.

Browser and CDN Support for Stale-While-Revalidate

Currently, stale-while-revalidate is honored in the response header by Chrome, Firefox, and Edge, and by major CDNs including Fastly, Akamai, Cloudflare, Vercel, and AWS CloudFront. Safari still does not apply it for browser-level HTTP caching, so coverage depends on where the caching happens.

Layer Support status Notes
Chrome / Edge Supported Honors the header for browser HTTP cache
Firefox Supported Honors the header for browser HTTP cache
Safari Not supported Ignores the directive for browser caching
Major CDNs Supported Fastly, Akamai, Cloudflare, Vercel, CloudFront

The practical takeaway is that CDN-side support is what matters most. When your CDN applies the directive at the edge, every visitor benefits regardless of their browser, because the stale-then-revalidate logic happens before the response ever reaches the client. Browser support is a bonus layer, not the foundation.

A concrete case: a marketing team assumed their Safari users were getting stale pages from the browser and spent a week debugging. The real behavior was that their CDN was handling revalidation for everyone, and Safari’s lack of header support changed nothing about what users saw. Once they checked the edge logs instead of the browser, the confusion cleared in minutes.

Final Thought on Stale-While-Revalidate

Stale-while-revalidate earns its place because it removes a false choice. You no longer have to pick between fast responses and reasonably fresh content, since the directive serves the cached copy instantly and refreshes it out of sight. The discipline is in the numbers: a max-age tied to real update frequency and an SWR window sized to your origin’s response time.

Treat it as one deliberate setting rather than a blanket toggle. Cache what tolerates a few seconds of drift, protect it with stale-if-error for outages, keep transactional data out of the cache entirely, and measure origin load after every change. Get those choices right and you deliver a faster site, a calmer origin, and an experience where users never feel the refresh happening.

Frequently Asked Questions About Stale-While-Revalidate

Is stale-while-revalidate safe to use?

Yes, when used correctly. It works best for content that can tolerate a few seconds of delay, such as product pages, news, and public APIs. Avoid using it for sensitive data like payments or personalised information.

What is the difference between stale-if-error and stale-while-revalidate?

Stale-while-revalidate improves speed by refreshing expired content in the background. Stale-if-error keeps a site available by serving cached content when the origin server fails or returns an error.

Does stale-while-revalidate work in all browsers?

No. Chrome, Firefox, and Edge support it, but Safari does not apply the directive for browser-level HTTP caching. However, most major CDNs support it at the edge.

What is a good stale-while-revalidate value?

A good value depends on how quickly your content changes and how long your origin takes to respond. A common starting point is 30–60 seconds for frequently updated content, then adjust based on performance and freshness needs.