Cache-Control header & Cache-Busting
Tags: webdev, performance
Published: 2022-07-10 02:06:09


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.