Cache-Control max-age, stale-while-revalidate
Until now, thanks to Last-Modified/If-Modified-Since or ETag/If-None-Match we mainly saved on bandwidth. However, the server always had to process each request.
Until now, thanks to Last-Modified/If-Modified-Since
or ETag/If-None-Match
we mainly saved on bandwidth. However, the server always had to process each request.
The server can instruct the client about using the stored resources for a certain duration, deciding if and when the client should revalidate the content and whether or not to do so in the background.
Real-world endpoints are not as instantaneous as those in this tutorial. The response may take milliseconds to generate, without even considering the location of the server relative to the requester!
Let's exacerbate the asynchronicity between server and client to highlight the need for the Cache-Control
Header.
Based on the previously implemented /only-etag
endpoint , register /cache-control-with-etag
. For the time being, it's identical, except that it waits three seconds before responding.
Also, add some log before dispatching the response
Let's visualize the problem. When you request a page from the browser, it enters a loading state for three seconds. Even if you refresh within that time, the browser makes the request anew, regardless of the ETag
or the Last-Modified
mechanisms. The page content persists because you're essentially staying on the same page. To observe the behavior more clearly, try reopening the page from a new tab or starting from a different site.
Most importantly, the server is hit on every request!
max-age
It is possible to instruct the browser to use the cached version for a certain duration. The server will set the Response Header Cache-Control
with a value of max-age=<seconds>
.
Give it another try now, and request the page. The first request makes the browser load for three seconds. If you open the same page in another tab, you'll notice it's already there, and the server wasn't contacted.
This behavior persists for the specified seconds. If, after the cache expires, a new request returns a 304 Not Modified
(thanks to the If-None-Match
header), the resource will be newly cached for that amount of time.
- ➕ Better UX
- ➕ Less load on the server
- ❔ If the resource changes, the client remains unaware until the cache expires. After expiration, with a different Etag, a new version will be displayed and cached.
When attempting a refresh while staying on the page, you might observe the loading delay, indicating that the server is being contacted. Make sure you're not doing a hard refresh, as it overrides the described behavior.
stale-while-revalidate
If your application needs resource validation but you still want to show the cached version for a better user experience when available, you can use the stale-while-revalidate=<seconds>
directive.
In this case, the browser is instructed to cache the response for 2 minutes. Once this period elapses, if the resource is requested within the next 5 minutes, the browser will use the cached resource (even if it's stale) but will perform a background validation call.
I want to emphasize the "even if stale" by playing around with the endpoint configured as above. Only soft refreshes are performed.
ℹ️ Info Tap/click the next items to show the related graph.
On the initial page request, it takes 3 seconds to load, and the response is cached for 2 minutes with an associated ETag. The client will include this ETag in the
If-None-Match
header for subsequent requests.Close and reopen the browser (or request from another page) within the 2-minute window: the page is instantly shown without hitting the server. %}
After the 2-minute cache expires, the server is contacted. The resource has not changed, the client receives
304 Not Modified
. The cache expiry date is extended with the providedmax-age
value. No need to update what the user is seeing. %}Now that the 2-minute window is open again, let's update the content of the resource using the db endpoint implemented in the previous blog post . Basically, next time the ETag will not match.
- Still within the 2-minute window, request the page again. Thanks to
max-age
, the browser shows it immediately. It proceeds with background validation as already seen in step 3. But this time the ETag does not match; the server responds with a200 OK
and provides a new ETag (which overrides the previous entry in the cache). %}
🔑 message Although the displayed content is stale, the browser updates its cache silently, preserving the user experience.
- Request the page anew; this time, it finally displays the latest stored version. If within the newly restarted 2-minute window, the server won't be contacted.
🔑 message Any request outside the time window indicated by stale-while-revalidate (which I recall, starts from the expiration of that of max-age), will behave like step 1 - blank state.
On This Page