I’ve finally configured caching on my site. Here’s what you need to know.

There’re 4 headers that enable caching: Cache-Control / Expires and Last-Modified / ETag . The former two are primary; they enable caching and instruct the browser on how long it should save a resource. The latter two are secondary and optional; browsers use them when the cached resource gets expired to check if it has changed. (If it hasn’t, browsers just take the expired one and keeps using it.)

In practice, you choose 2 of these headers: one primary and one secondary – and use them together. Using all four headers isn’t very practical – the browser will rely on only two of them.

I recommend choosing Cache-Control and ETag . Cache-Control lets you configure the caching details that Expires can’t. ETag , according to MDN, is more reliable than Last-Modified .

Use Cache-Control and ETag

Imagine you have a file called pic.gif . This one:

This is how its lifecycle will look:

The browser requests pic.gif . The server sends the response with, for example, these caching headers: Cache-Control: max-age=60 ETag: deadbeef123 The user refreshes the page. If less than 60 seconds have passed (60 is a value from Cache-Control: max-age ), the browser doesn’t make any requests and just takes pic.gif from the cache. The user refreshes the page. If more than 60 seconds have passed (60 is a value from Cache-Control: max-age ), the browser sends a request for pic.gif and attaches the If-None-Match: deadbeef123 header. ( deadbeef123 is a value from the ETag header that the browser has received.) When the server receives the request, it reads pic.gif and calculates its ETag ( ETag is a hash which changes when the file changes). And then: if the calculated ETag is still deadbeef123 , the file hasn’t changed. The server returns an empty response with the 304 Not Modified status.

is still , the file hasn’t changed. The server returns an empty response with the status. if the calculated ETag is different, the file has changed. The server returns the content of pic.gif with the 200 OK status and new Cache-Control and ETag headers.

Servers respond with either 304 Not Modified or with a new resource

Total immutability#

Usually, on the third step (↑), when the resource expires, the browser makes a request to the server. If the file has changed, the browser downloads the new version. But if it hasn’t, the browser receives 304 Not Modified and doesn’t download anything. That’s nice, but the extra request to the server is still there.

If you know that the resource will never change, and checking this doesn’t make any sense, you can send the immutable value in the Cache-Control header:

Cache-Control: max-age=60, immutable

or simply

Cache-Control: immutable

After this, the browser will always consider the cached resource as valid and will never send a verification request for it. It’s like if you set max-age to 1000 years.

Cache-Control: immutable is a new header value. At the moment, it works only in Firefox and Edge.

Use Cache-Control: immutable to prevent any additional requests

On my site (except this blog), I enabled Cache-Control: immutable for all images, styles, and scripts. To force the browser to re-download the file if it changes, I append the last change date to the file name:

/images/pic.gif?hash=1499433448

This way, the browser will send a request for the file only when the file (and, therefore, its name) changes.

This approach—including a dynamic value into the file name—is called versioning. Versioning is a common practice, and I recommend enabling it in your app.

Unlike versioning, Cache-Control: immutable hasn’t become a common practice yet. However, it’s already being used by e.g. Facebook, so you can try enabling it too.

Use versioning, it’s a common practice

Summing up#

Use Cache-Control and ETag headers for caching

and headers for caching Implement versioning for cache invalidation

Try Cache-Control: immutable if you have resources that will never change

Related