I decided to use the Recursive font on my site once again and when it went live, they kept popping in between subsequent reloads. Looking into it, it turns out that this was due to how Netlify caches things.
What does Netlify do?
In essence, they include this header:
cache-control: public, max-age=0, must-revalidate
As they put it, the header says please cache this content, and then do not trust your cache
. This is useful for assets that change frequently as the browser will always check if an asset has changed before fetching it by verifying the ETag
header.
Seems ok. What’s the problem?
The issue I had was that certain assets on this site (like CSS, JS, and font files) have cache busted URLs where the URL either has a hash or version number.
<link rel=stylesheet href=/fonts.1baec3efa7adb966c2c5c92e8258e75a350c5151d2679de09650f4536b185be8.css>
<link rel=stylesheet href=/main.ba62e1ad92ea86749a3f67394100f75f0044a087c2101678a42e21ddffdaaa3f.css>
Note the hashes in the hrefs. That corresponds to the file’s hash. If the underlying file changes, the hash would as well.
There’s no need to constantly revalidate such an asset as it will never change. If the asset did change, it would have a new URL that would be cached on its own. As fast as Netlify’s CDN is and however much they want to include an ETag
, revalidation is still pointless overhead for fingerprinted URLs.
How To Fix This
To fix this, I used the immutable
cache-control header which tells the browser that the asset will not be updated as long as it’s fresh. Note that the maximum caching age is a year which is about 31,536,000 seconds).
Cache-Control: public, immutable, max-age=31536000
To do this, you need to provide custom headers. I used an _headers
file which looks like this:
/*.woff2
Cache-Control: public, max-age=31536000, immutable
/*.css
Cache-Control: public, max-age=31536000, immutable
/*.js
Cache-Control: public, max-age=31536000, immutable
This works on my site since CSS and JS files will always be fingerprinted1, and the .woff2
font files contain the version in the URL so if the font is updated, so will the URL.
Conclusion
After doing this, the site loaded a bit faster and the pop-in didn’t go away. It’s an improvement, though I need to quantify it at some point. That’s a benefit of going beyond the defaults. I know I put Netlify here, but I’m sure you can apply this to any host that let’s you change HTTP headers
This is all possible thanks to Hugo which comes with a good asset pipeline. If Hugo didn’t have this, or I decided not to use cache busted URLs, sticking to must-revalidate
would be fine.
Interestingly, a few days after I solved this, I found a guide by Simon Hearne which goes into caching headers best practices which covers other scenarios.
The next performance optimization I want to do after this is preloading assets. The last time I tried it nothing changed but I hope it’ll be different this time.
-
Even if I include a library through NPM, I’d still cache bust it. ↩︎
Top comments (0)