developers balance between immediacy—loading cached content right away—and
freshness—ensuring updates to the cached content are used in the future. If
you maintain a third-party web service or library that updates on a regular
schedule, or your first-party assets tend to have short lifetimes, then
stale-while-revalidate may be a useful addition to your existing caching
Browsers that don’t support
stale-while-revalidate will silently ignore that
configuration value, and use
as I’ll explain shortly…
What’s it mean?
Let’s break down
stale-while-revalidate into two parts: the idea that a cached
response might be stale, and the process of revalidation.
First, how does the browser know whether a cached response is “stale”? A
response header that contains
stale-while-revalidate should also contain
max-age, and the number of seconds specified via
max-age is what determines
staleness. Any cached response newer than
max-age is considered fresh, and
older cached responses are stale.
If the locally cached response is still fresh, then it can be used as-is to
fulfill a browser’s request. From the perspective of
there’s nothing to do in this scenario.
But if the cached response is stale, then another age-based check is performed:
is the age of the cached response within the window of time covered by the
If the age of a stale response falls into this window, then it will be used to
fulfill the browser’s request. At the same time, a “revalidation” request will
be made against the network in a way that doesn’t delay the use of the cached
response. The returned response might contain the same information as the
previously cached response, or it might be different. Either way, the network
response is stored locally, replacing whatever was previously cache, and
resetting the “freshness” timer used during any future
However, if the stale cached response is old enough that it falls outside the
stale-while-revalidate window of time, then it will not fulfill the browser’s
request. The browser will instead retrieve a response from the network, and use
that for both fulfilling the initial request and also populating the local cache
with a fresh response.
Below is a simple example of an HTTP API for returning the current time—more
specifically, the current number of minutes past the hour.
In this scenario, the web server uses this
Cache-Control header in its HTTP response:
Cache-Control: max-age=1, stale-while-revalidate=59
This setting means that, if a request for the time is repeated within the next 1
second, the previously cached value will still be fresh, and used as-is, without
If a request is repeated between 1 and 60 seconds later, then the cached value
will be stale, but will be used to fulfill the API request. At the same time,
“in the background,” a revalidation request will be made to populate the cache
with a fresh value for future use.
If a request is repeated after more than 60 seconds, then the stale response
isn’t used at all, and both fulfilling the browser’s request and the cache
revalidation will depend on getting a response back from the network.
Here’s a breakdown of those three distinct states, along with the window of time
in which each of them apply for our example:
What are the common use cases?
While the above example for a “minutes after the hour” API service is contrived,
it illustrates the expected use case—services that provide information which
needs to be refreshed, but where some degree of staleness is acceptable.
Less contrived examples might be an API for the current weather conditions, or
the top news headlines that were written in the past hour.
Generally, any response that updates at a known interval, is likely to be
requested multiple times, and is static within that interval is a good candidate
for short-term caching via
stale-while-revalidate in addition
max-age increases the likelihood that future requests can be fulfilled from
the cache with fresher content, without blocking on a network response.
How does it interact with service workers?
Using stale-while-revalidate via a
Cache-Control header shares some
similarities with its use in a service worker, and many of the same
considerations around freshness trade-offs and maximum lifetimes apply. However,
there are a few considerations that you should take into account when deciding
whether to implement a service worker-based approach, or just rely on the
Cache-Control header configuration.
Use a service worker approach if…
- You’re already using a service worker in your web app.
- You need fine-grained control over the contents of your caches, and want to
implement something like a least-recently used expiration policy. Workbox’s
module can help with this.
- You want to be notified when a stale response changes in the background during
the revalidation step. Workbox’s
Broadcast Cache Update
module can help with this.
- You need this
stale-while-revalidatebehavior in all modern browsers.
Use a Cache-Control approach if…
- You would rather not deal with the overhead of deploying and maintaining a
service worker for your web app.
- You are fine with letting the browser’s automatic cache management prevent
your local caches from growing too large.
- You are fine with an approach that is not currently supported in all modern
browsers (as of July 2019; support may grow in the future).
If you’re using a service worker and also have
for some responses via a
Cache-Control header, then the service worker will,
in general, have “first crack” at responding to a request. If the service worker
decides not to respond, or if in the process of generating a response it makes a
network request using
then the behavior configured via the
Cache-Control header will end up going
Hero image by Samuel Zeller.