Skip to content
stoic man
January 3, 2021

How I load my web fonts in Gatsby

Preloading self-hosted web fonts, for greater performance

In my website I use two typefaces Inter & Fira Code. The first font-family covers everything on this website except the code blocks.

My main pet peeve is that before Inter loads, the visitor will briefly notice a fallback font. This is pretty much inevitable unless:

  1. You use a loader (suboptimal)
  2. Your website transmits everything during the first connection roundtrip (~14K, sometimes impossible)
  3. You want to stick to the fallback font

But we'll work with what to have.

Other options

Using Google fonts

  • Putting an external dependency on Google is not my cup of tea, privacy-wise
  • You're not in control of the updates
  • Since Chrome v86, cross-site resources like fonts can’t be shared on the same CDN. No performance boost using Google Fonts anymore

Using fonts from NPM

Like typefaces or fontsource.

  • The fonts will be parsed by Webpack, will have a hash assigned to them breaking cache. There are workarounds but the alternatives are simpler
  • You can’t preload the fonts easily

How to do it

1. Self-host the fonts

I like to manually fetch the desired fonts from the fountsource repo. By selecting the specific language subsets you need, you can trim a ~150kb font down to ~40kb, which is bonkers.

To avoid pulling multiple fonts, and since I don’t care for old browsers, I use the variable version of Inter & Fira Code (support), served as woff2.

File structure
/static
  /fonts
    - fira-code-var-latin.woff2
    - inter-var-latin.woff2

2. Write the font declarations

And import the file from gatsby-browser.js

global.css
@font-face {
  font-family: 'Fira Code';
  font-style: normal;
  font-display: swap;
  font-weight: 300 700;
  src: url('./fonts/fira-code-var-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/inter-var-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

3. Preload the fonts

If you don’t have html.js, check the process here. I prefer to only use react-helmet for properties that differ from page to page, so anything else lives in html.js for me.

You'll notice that I don’t include Fira Code here, as it’s not a critical resource.

html.js
<link
  rel="preload"
  href="/fonts/inter-var-latin.woff2"
  as="font"
  crossorigin="anonymous"
  type="font/woff2"
/>

4. Cache them hard

I deploy my website in Netlify, and I use gatsby-plugin-netlify to handle any configuration.

Here’s how I cache my fonts, forever.

gatsby-config.js
{
  resolve: 'gatsby-plugin-netlify',
  options: {
    headers: {
      '/fonts/*': [
        'Cache-Control: public',
        'Cache-Control: max-age=365000000',
        'Cache-Control: immutable',
      ],
    },
  },
},

Otherwise, without the said plugin, a simple _headers file in the public folder will suffice.

_headers
/fonts/*
  Cache-Control: public
  Cache-Control: max-age=365000000
  Cache-Control: immutable

Fin

Before trying to install a font-related Gatsby plugin, why not consider this approach?

👋