A while ago, I found a bug in one of my company’s earliest PWAs; some users’ devices did not fully receive the latest update. Investigating this issue led me down the rabbit hole of cache-busting and caching strategies.
This article focuses on the Cache-Control Header and possible cache-busting strategies. I might publish a second part, that will focus on caching with service workers.
Cache-Control Header
The Cache-Control header provides the ability to manage the cache per asset. It can have one or multiple directives.
public
This directive allows any cache to store the resource (incl. browser caches and CDNs). Resources that do not include user-specific data could be cached using this directive.
If a resource specifies other directives, but neither public
nor private
, public
is set implicitly.
Cache-Control: public
private
This directive indicates that only a browser can cache the resource. private
is useful for responses that are unique to each user.
Cache-Control: private
no-store
If this directive is set, no cache will store the resource. Use this directive for resources that include sensitive or time-critical information.
Cache-Control: no-store
no-cache
Caches will always re-validate resources that have this directive.
The cache will ask the server for a newer version. If it has one, it replies with a 200 and the new file to download and use. If the cached version is the latest one, it responds with 304. The cache then serves the asset it already has.
Since no-cache
will always trigger a re-validation, it cannot be combined with the max-age
directive.
Cache-Control: no-cache
max-age
This directive sets a time (in seconds) within the asset is safe to use. After that time has passed, the file will be considered stale. Stale files will be re-validated the next time the client requests the file and restarts the max-age
. Note that caches might eject stale files. In that case the client will need to download the resource again.
If the user explicitly refreshes the page, the browser will re-validate the resource even though the max-age
hasn’t passed yet.
Cache-Control: max-age=60
s-maxage
This directive overwrites the max-age
directive for shared caches (like proxy servers or CDNs).
Cache-Control: max-age=86400, s-maxage=3600
must-revalidate
Use this directive in combination with max-age
.
must-revalidate
prevents caches from ejecting the resource.
Again, refreshing the page will cause re-validation even if the specified time hasn’t passed yet.
Cache-Control: must-revalidate, max-age=600
proxy-revalidate
This directive overwrites the must-revalidate directive for public caches.
stale-while-revalidate
This directive allows caches to serve the stale file while re-validating the resource for a specified amount of time (in seconds).
Cache-Control: max-age=31536000, stale-while-revalidate=86400
stale-if-error
If this directive is set, the cache will serve the stale resource for the specified amount of seconds if the server responds with a 5xx error upon re-validation.
Cache-Control: max-age=2419200, stale-if-error=86400
no-transform
This directive prevents intermediaries from modifying responses. For example, a telco provider might optimise images by default. You most likely want to prevent that because it would likely cause a loss in quality.
Since intermediaries are not allowed to modify responses on HTTPS connections, this directive will only affect HTTP connections.
Cache-Control: no-transform
immutable
This directive specifies the amount of time within the resource will not change. During that time, caches will not re-validate it, not even on manual page refreshes, nor download newer versions. So, ensure a good cache-busting (see below) strategy before using the immutable
directive!
Cache-Control: max-age=31536000, immutable
Cache-busting
Cache-busting strategies define how cached resources can be updated or replaced. These are some strategies that you can use:
Fingerprinting filenames
A random hash is added to directories, per example: images-rzt87032ztr. This is usually easier to set up but could cause a lot of unnecessary traffic. Caches will need to download all resources within the folder, even though only a couple might have changed.
Query String
A specific version is requested via parameter, per example: style.css?v=1.0.0. This provides cache-busting per individual file but might still be an insufficient solution since many proxy servers and CDNs omit query strings while serving from the cache. Therefore these caches cannot be busted using this strategy.
Conclusion
What you should take away:
Work out a cache-busting strategy first, then define a caching strategy.
Thanks for reading.
Top comments (1)
If you're using caddy, add following lines to the config file (usually
/etc/caddy/Caddyfile
) to enableCache-Control
for all requests:or to enable
Cache-Control
for specific request /css/style.css