stale-while-revalidate Explained
Updated April 2026
stale-while-revalidate eliminates the latency penalty of cache revalidation. The browser or CDN serves the stale cached version immediately, then fetches a fresh copy in the background — no user waits.
The problem it solves
Normal caching with max-age: when the cache expires, the next request blocks while the browser fetches a fresh copy. The user waits for the full network round-trip. With stale-while-revalidate, that wait disappears.
How it works
Cache-Control: max-age=60, stale-while-revalidate=300
- 0–60 seconds: Response is fresh. Served directly from cache, no network request.
- 60–360 seconds: Response is stale but within the SWR window. Served immediately from cache (user sees instant response). Simultaneously, a background request fetches a fresh copy from origin.
- After 360 seconds: Cache is expired. Next request goes to origin, user waits for full response.
Real-world example
A news API that updates every few minutes:
Cache-Control: public, max-age=60, stale-while-revalidate=300
Users always get an instant response. Content may be up to 6 minutes old in the worst case (60s max-age + 300s SWR window). For most news sites this is perfectly acceptable.
Adding stale-if-error for resilience
Cache-Control: public, max-age=60, stale-while-revalidate=300, stale-if-error=86400
If the background revalidation fails (origin is down), continue serving the stale response for up to 24 hours instead of showing users an error. This combination makes your site resilient to brief origin outages.
Nginx config
location /api/ { add_header Cache-Control "public, max-age=60, stale-while-revalidate=300" always;
}
# For proxy caches (Nginx as reverse proxy):
proxy_cache_use_stale updating error timeout;
proxy_cache_background_update on;
Cloudflare
Cloudflare honours stale-while-revalidate from origin headers. As of 2024, revalidation is fully asynchronous — the first request after expiry immediately receives stale content while Cloudflare fetches fresh from origin.
Vercel
{ "headers": [ { "source": "/api/(.*)", "headers": [{"key": "Cache-Control", "value": "public, max-age=60, stale-while-revalidate=300"}] } ]
}
Browser support
All modern browsers support stale-while-revalidate since Chrome 75 (2019) and Firefox 68 (2019). Safari added support in 2022. Browsers that do not recognise the directive silently ignore it and use max-age alone.
Safari support for stale-while-revalidate
Safari was the last major browser to add support. Safari 15.4 (March 2022) added full support. Before that, Safari silently ignored the directive and fell back to max-age alone — no errors, no broken pages. Safe to use for all browsers today.
/* Safe for all browsers including Safari < 15.4 */ Cache-Control: public, max-age=60, stale-while-revalidate=300 /* Safari < 15.4: ignores stale-while-revalidate, uses max-age only. More revalidations, zero broken pages. */
Browser support
| Browser | Version | Date |
|---|---|---|
| Chrome | 75+ | June 2019 |
| Firefox | 68+ | July 2019 |
| Safari | 15.4+ | March 2022 (iOS 15.4 / macOS Monterey) |
| Edge | 79+ | January 2020 |
| Samsung Internet | 12.0+ | 2020 |
CDN support
| CDN | Support | Notes |
|---|---|---|
| Cloudflare | Full | Asynchronous background revalidation at the edge |
| Vercel | Full | Honoured at CDN layer automatically |
| Fastly | Full | Use surrogate-control for finer control |
| Netlify | Full | Honoured at CDN layer |
| AWS CloudFront | Partial | Passes header through but no edge background refresh |
When not to use it
Does Safari support stale-while-revalidate?
Yes. Safari added support in version 15.4 (March 2022). All modern browsers now support stale-while-revalidate:
| Browser | Support since |
|---|---|
| Chrome | 75 (June 2019) |
| Firefox | 68 (July 2019) |
| Safari | 15.4 (March 2022) |
| Edge | 79 (January 2020) |
Browsers that don't recognise the directive silently ignore it and fall back to max-age alone — so it's safe to deploy without a fallback.
- Real-time data (stock prices, live scores) — staleness is unacceptable
- Authenticated responses — use
privateorno-storeinstead - Resources that must always be fresh (security-critical headers, CSP policies)