How to Cache Fonts on Netlify?

How to Cache Fonts on Netlify?

Learn how to use HTTP Cache to boost your website's performance.

This article was originally published at zsgomori.dev. Head over there if you like this post and want to read others like it!

Font optimisation techniques, such as pre-loading, not only can contribute to more efficient Core Web Vitals scores, but also can help prevent your visitors from experiencing FOUT and FOIT. Our today’s topic will focus on HTTP Cache and how to utilise it to cache self-hosted fonts on Netlify.

We will start off with a brief brush up on how HTTP Cache works and what doors it unlocks. Then, I’ll walk you through the steps I followed to cache my fonts on this website.

TL;DR

For those of you who want to cut to the chase, hit the copy button and paste it into your netlify.toml configuration file:

[[headers]]
  for = "/fonts/*"
  [headers.values]
    cache-control = '''
    max-age=365000000,
    public
    immutable'''

You may need to vary either the route or the cache-control values — or even both! - to suit your particular needs.

How Does HTTP Cache Work?

  1. When the browser encounters a request to an asset while parsing your HTML's metadata, it checks whether there’s already a valid cache response in its cache, before reaching out to the web server.
  2. If there’s no cache that would fulfill the request or the cache is stale, the request gets forwarded to the web server.
  3. The web server processes the request and acts accordingly — it sends the requested asset back over the wire. Besides, if configured, it augments the response headers with [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) to specify the directives for caching mechanisms (for example, for how long the cache will be considered fresh).

In a nutshell, that’s it. One of the major benefits of caching is that on subsequent requests to the same asset, the browser does not need to carry out the transmission of the request towards the server over and over again.

Instead, so long as the cached response’s lifetime has not expired yet, the browser will intercept the request and reuse the previously cached response to fulfill it. Consequently, we are gaining on speed by reducing the roundtrips between the browser and the server.

Explaining the ins and outs of the HTTP Cache flow, making sure of cache freshness with ETag and Last-Modified in particular, is beyond the scope of this article. If you’re looking for an in-depth explanation, consult with the MDN docs.

Caching Fonts on Netlify

We have already touched on the configuration part — it’s as straightforward as adding an additional header to outgoing responses. When it comes to Netlify, hooking into and altering their default caching behaviour is literally a piece-of-cake-task.

It should be fairly straightforward even if you use a different platform (e.g. Vercel) or run your web server on your own.

For the sake of completeness, here’s the exact same snippet from the TL;DR section:

[[headers]]
  for = "/fonts/*"
  [headers.values]
    cache-control = '''
    max-age=365000000,
    public
    immutable'''

In addition to the file-based configuration approach, you can also define caching policies in the _headers file.

If TOML is somewhat confusing, the JSON equivalent may help better grasp what's going on:

{
  headers: [
    {
      for: "/fonts/*",
      values: {
        "cache-control": "max-age=365000000,public,immutable"
      }
    }
  ]
}

Let me briefly expand upon what’s happening here.

The for keyword declares the path or URL which the headers will be added to. In my case, I self-host my fonts and they live inside the static/fonts folder, which ultimately will be exposed via the /fonts route. Therefore, the following link pointing to the Poppins font on my site will match the /fonts/* pattern:

<link
  rel="preload"
  href="/fonts/Poppins-Regular.woff2"
  as="font"
  type="font/woff2"
  crossorigin="anonymous"
/>

Onto the values keyword. As the name suggests, here you can tie the header values up to the route it’s grouped under. The max-age=<seconds> directive dictates the lifetime of a resource, that is, the maximum amount of time a resource is considered fresh. Given that the fonts on my site are quite unlikely to change in the foreseeable future, I store them for more than a year.

You can learn more about the available set of directives on MDN.

And that's a wrap, folks!

Thanks for reading the article! I genuinely hope you learned something new! Feel free to leave a comment or hit me up on Twitter with any questions.