Freshness vs. speed: the stale-while-revalidate cache strategy
Serving content that is both immediate and up to date is a constant balancing act. The HTTP stale-while-revalidate caching directive offers a way to ease the tension by defining a window in which stale content can be served immediately while the cache is refreshed in the background. Support for this directive in the Cache-Control response header ships in Chrome 75 and Firefox 68. Browsers without support simply ignore the directive and stick to standard max-age semantics.
The directive is designed for scenarios where an asset or API response changes on a predictable schedule, where staleness for a short period is acceptable, and where you want to avoid having every request block on the network. It pairs specifically with max-age in the Cache-Control header.
How time-based revalidation works
Decoding stale-while-revalidate comes down to two age checks, both driven by max-age and the stale-while-revalidate window.
The first check is the familiar freshness check: if the cached response is not yet max-age seconds old, it is considered fresh and is served directly with no additional work. The interesting behavior appears only after a response goes stale.
Once a response is older than max-age, its age is compared against the extension window offered by stale-while-revalidate:
- Populated within the window: If the response's age is beyond
max-agebut still within thestale-while-revalidateperiod, the stale copy fulfills the current request without delaying it. Meanwhile, a background revalidation request goes to the network. Its result—which may or may not differ from the previous body—replaces the cached copy and resets the freshness timer for future requests. - Outside the window: If the cached response is older than the combined total of
max-ageplusstale-while-revalidate, the browser ignores the cache entirely and obtains the response from the network, using that new response for both the current request and to populate the cache.
Consider a time API that reports the number of minutes past the hour, served with the following header:
Cache-Control: max-age=1, stale-while-revalidate=59
This means roughly:
- Repeated requests within 1 second hit a perfectly fresh cache, and no revalidation occurs.
- Requests arriving 1 to 60 seconds after the last response fulfill the API call with the stale copy while a background request refreshes the cache.
- Requests arriving after 60 seconds miss the reuse window and must wait for a full network round-trip.
Use cases for the directive
But the idea isn't limited to contrived examples. Practical targets include weather API feeds and hourly news headlines—anything that updates on a known cadence, sees repeated requests, and remains valid and unchanged between updates. Short-term caching with max-age was already the go-to option for such data; adding stale-while-revalidate makes the fallback substantially better by serving from the cache in the stale period and avoiding a blocking network trip.
Delivering a fresh-first version of stale content
For API users, the benefit shows up first in later requests: if an earlier network call refreshed the cache during the stale window, then everyone else is now getting a fresh answer. For products that need to handle traffic spikes, this helps smooth out bursty loads.
Data that doesn't change much yet is highly requested (e.g., a database drive that updates once a day) carries the same benefit. The pattern adds almost no client-side complexity.
Service worker vs. header integration
Many developers will recognize stale-while-revalidate from common service worker recipes. The header approach and the service worker version share the same conceptual goal, and similar freshness / maximum-lifetime trade-offs apply. The implementation should be chosen deliberately, because the two interact in a defined order: if your site runs a service worker, it generally gets the first chance to answer a request. Failing that, the service worker may generate a response by making a network request with fetch(). In that case, the Cache-Control header behavior takes over.
The header approach suits many cases; the service worker approach suits others. Here's where each plays best:
When a service worker is the right fit
- Your app already operates via a service worker for offline-first or other reasons.
- You need granular control over exactly which URLs or requests are cached, plus logic to prune your caches—for example an least-recently-used expiration policy, optionally with Workbox’s Cache Expiration module.
- You need to know when the background revalidation has resulted in an update to the live content, as implemented in Workbox’s Broadcast Cache Update module.
- You must support this exact caching pattern in every modern browser, since the workbox implementation covers all of them — the header alone doesn't work everywhere yet.
When a Cache-Control header approach works fine
- You don't want the operational overhead of keying up and maintaining a service worker.
- You accept the browser’s automatic cache-eviction strategies to bound cache size.
- You accept user agents without support for the directive will use their standard (non-stale) behaviors temporarily and fall back to
max-age.
Know the current limits
As of publication, header-based stale-while-revalidate isn't universal – Chrome 75+ and Firefox 68+ implement it, but other active engines (notably Safari) don't. Treating the header as progressive enhancement—a fallback to max-age—keeps your app correct everywhere without weakening the signature. Broader support may come over time, per the relevant specifications.



