I’ve finally configured caching on my site. Here’s what you need to know.
Headers#
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
.
Lifecycle#
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 takespic.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 forpic.gif
and attaches theIf-None-Match: deadbeef123
header. (deadbeef123
is a value from theETag
header that the browser has received.)When the server receives the request, it reads
pic.gif
and calculates itsETag
(ETag
is a hash which changes when the file changes). And then:- if the calculated
ETag
is stilldeadbeef123
, the file hasn’t changed. The server returns an empty response with the304 Not Modified
status. - if the calculated
ETag
is different, the file has changed. The server returns the content ofpic.gif
with the200 OK
status and newCache-Control
andETag
headers.
- if the calculated
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.
Versioning#
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.
Summing up#
- Use
Cache-Control
andETag
headers for caching - Implement versioning for cache invalidation
- Try
Cache-Control: immutable
if you have resources that will never change