5 steps to faster web fonts

Fine-tune your font files and optimise your loading strategy for maximum speed + minimum FOUT

12th May 2021 2,315 words

In my previous post, I wrote about system fonts and their advantages over web fonts. I encouraged a ‘system fonts first’ approach, arguing that, compared to system fonts, web fonts (a) can negatively impact performance, (b) use more data, and (c) increase your site’s energy consumption. But a web without web fonts would be a far less interesting one — maybe by using web fonts a little more responsibly we can get all their benefits, while minimising the disadvantages.

In part one of this guide, I’m going to cover the five methods for improving web font performance that I think offer the greatest gains for the least effort.

Credit goes to Zach Leatherman, who has written at length about web fonts on his site. All of his articles are worth reading, especially The Font Loading Checklist and A Comprehensive Guide to Font Loading Strategies (which is indeed very comprehensive), both of which came in very useful while I was putting this post together.

In this post, I’m going to be using two terms that are often used interchangeably but which traditionally refer to different things:

  • A typeface is a full family of fonts sharing a common design. A typeface can include any number of weights or styles (and in the days of physical metal or wood type, sizes too). Helvetica is an example of a typeface. You can think of a typeface like a font-family.
  • A font is a single weight and style of a typeface. With physical type, each font would come in its own box containing glyphs of a specific size, weight, and style. e.g. '10-point Helvetica Bold Italic'. The vector-based design of modern digital fonts allows a single font to be scaled up and down infinitely, but you’ll still need a separate file for each weight and style (unless you’re using variable fonts, but that’s a topic for part two).

1. Use the most modern file formats

Web Open Font Format 2.0 (WOFF2) is, at the time of writing, the smallest and most efficient file format for web fonts. When using @font-face at-rules in your CSS, ensure the WOFF2 font appears before older, less efficient, file formats such as TTF. The browser will use the first font in the list it understands, even if it’s a larger file.

@font-face {
font-family: 'Typefesse';
src: url('typefesse.woff2') format('woff2'),
url('typefesse.woff') format('woff');
}

Unless you need to support IE8, you don’t need anything other than WOFF2 and WOFF. If you don’t need to support IE11, you only need WOFF2.

If you only have a TTF file (for example, if you’ve downloaded the font from Google Fonts), you’ll need to convert it using a tool like Online Font Converter. If you’re not using a font with a fully open source license, first check whether the licence permits this.

2. Use the font-display descriptor

There are two acronyms you’ll see a lot when you start delving into font loading strategies:

  1. Flash of Invisible Text (FOIT) is the period of time when text is invisible before the browser has downloaded a web font.
  2. Flash of Unstyled Text (FOUT) is the period of time where text is rendered in a fallback font before the browser has downloaded a web font.

Neither of these are ideal, but if you’re using web fonts, one of them is probably going to happen the first time a user visits your website (hopefully, by the second page load, the browser will be able to serve the fonts from its cache). If we take our font-face at-rule from before and add a font-display descriptor, we can tell the browser which one we’d prefer.

@font-face {
font-family: 'Typefesse';
src: url('typefesse.woff2') format('woff2'),
url('typefesse.woff') format('woff');
font-display: swap;
}

There are five possible values for font-display: the first, auto is the browser’s default behaviour (most browsers favour FOIT). Here are the other four:

swap

A chart showing the behaviour of font-display: swap; (text version below)

swap tells the browser we want it to show text using a fallback font until the web font is loaded (i.e. we’d prefer a FOUT). Whether this takes 5 seconds or 5 minutes, as soon as the font is loaded it will be swapped in. This is a good base because it lets website visitors start reading your content right away, but be sure to choose a similar fallback (we’ll cover fallbacks in part two of this series) to prevent a big layout shift when the fonts are swapped.

block

A chart showing the behaviour of font-display: block; (text version below)

If we’d rather the browser hides text until the web font is loaded (i.e. we’d rather a FOIT), we can use font-display: block. Text won’t remain invisible forever though: if the font doesn’t load within a certain period (usually three seconds), the browser will use the fallback font anyway, swapping in the web font once it has loaded.

If this seems to you like the best option because you think the FOUT looks bad, remember that when text is invisible, your page isn’t useable and your content isn’t readable.

fallback

A chart showing the behaviour of font-display: fallback; (text version below)

fallback is similar to swap with two differences:

  1. It begins with an incredibly small (~100ms) 'block' period where text is hidden, after which it shows the fallback font.
  2. If the web font doesn’t load within a short period (~3s), the fallback font will be used for the rest of the page’s lifetime.

If you’re not fussed whether the user sees your web font the first time they visit your site (chances are they’re not that fussed themselves), fallback is a good choice.

optional

A chart showing the behaviour of font-display: optional; (text version below)

optional is similar to fallback, but it gives the font an extremely short period of time (~100ms) to load, after which it won’t be swapped. It does, however, have an additional feature where it lets the browser decide to abort the font request if the connection is too slow for the font to load.

Each font on your page will have its own FOIT/FOUT period — fonts are swapped individually as they load, not when they’ve all loaded. This can lead to some unfortunate behaviour (see the Mitt Romney Web Font Problem). For full control over font loading, you’ll need to look into JavaScript solutions (which we’ll cover in part 2).

3. Preload your font files

To minimise the FOIT/FOUT period, we want to load our web font files as quickly as possible. Using <link rel="preload"> in our HTML <head>, we can tell the browser to start fetching our fonts earlier. Add the following tag towards the top of your <head> (before any CSS), setting the href attribute to the URL of your font file:

<link rel="preload" href="/typefesse.woff2" as="font" type="font/woff2" crossorigin>

By adding this tag, we’re telling the browser to start loading our font file right away, whereas normally it wouldn’t begin until it’s found a reference to the specific font in your CSS and found a DOM element which uses it.

Browsers are usually smart enough to only download fonts if they’re needed on the current page. Using preload overrides this behaviour, forcing the browser to download a font even if it isn’t used. For this reason, only ever preload a single format of each font (WOFF2 if you have it).

The more fonts you preload, the less benefit you’ll get from this technique, so prioritise those fonts which appear ‘above the fold’ (the first 100vh the user sees without scrolling).

You can read more about preloading in this article by Yoav Weiss: Preload: What Is It Good For?

4. Subset your font files

By subsetting a font, we can generate a new smaller font file which only includes the glyphs (a glyph is an individual character or symbol) we need. I used the Font Subsetter tool on Everything Fonts to subset the font used for headings on this site, Space Grotesk Bold, to only include characters in the ‘Basic Latin’ range. This reduced the filesize of the WOFF2 version from 30kB to just 7kB.

Subsetting is a powerful tool, but it does come with some potential downsides. If you’re building a website that displays user-generated content, people’s names, or place names you should consider characters other than the 26 standard letters, 10 numbers, and handful of symbols common in English writing.

As a minimum, you should think about diacritics: glyphs that appear above or below a character which alter its pronunciation. These are common in languages including French, Spanish, Vietnamese, as well as transliterated (or ‘romanised’) text from alphabets like Greek or Hebrew; they also appear in loanwords (words adopted from another language).

If you subset too aggressively, you could even end up with a mix of fonts in the same word.

A screenshot of the word 'Papier-mâché' in the font Space Grotesk, but the two letters with diacritics are in a different font.
If I wanted to pivot to writing about crafts, I might need to adjust the subset font file I use for headings. Note how the shapes of the ‘â’ and ‘é’ (with diacritics) don’t match the versions of those letters without diacritics.

Fortunately, you don’t have to manually check every page on your site for different glyphs. Glyphhanger is a command line tool, which does two things: firstly, it looks at your webpages and determines the Unicode character ranges used (these ranges correspond to a script or language. e.g. ‘Basic Latin’, ‘Cyrillic’, ‘Thai’); secondly it subsets a font file, outputting a new version containing only the characters in the specified ranges.

It can be a little tricky to get started with Glyphhanger (you’ll need python and pip) — Sara Soueidan’s explains how she got it working here: How I set up Glyphhanger on macOS for optimizing and converting font files for the Web.

As with changing file formats, make sure the licence for your font permits subsetting.

5. Self-host your fonts

This isn’t a universal rule like most of the other points here. There are two good reasons why you might want to use a hosted service like Google Fonts or Adobe Fonts:

  1. They’re often the cheapest or only legal way to use certain typefaces on the web: If you’ve got no choice but to use one of these services, find out if it supports subsetting or adding font-display descriptors.
  2. They’re convenient: Copying and pasting a line of HTML into your site’s <head> is going to be faster than the alternative: downloading font files, converting and subsetting font files, then writing @font-face at-rules for each weight and style.

If you’re still using Google Fonts purely because of the convenience, take a look at google-webfonts-helper. This tool lets you build a custom web font bundle from the complete set of Google fonts, define the weights and character sets you need, then gives you a single download containing all the CSS and font files (in the latest formats) you need.

Web font Myth #1

You may have heard the claim (which is repeated by Google Fonts[1]) that if a user has previously visited a site which loads the same fonts from the same source, the browser doesn’t need to download them again because they’re cached.

This may once have been true, but I can find no evidence that this is a regular enough occurrence to make a real difference. In fact, both Google Chrome and Safari explicitly prevent sharing of cached third-party resources across different domains because of tracking concerns[2].

Here are a list of good reasons not to use a hosted service and self-host your fonts instead:

Performance

Domain lookups take time; you can use preconnect resource hints to mitigate the issue, but there will always be a performance penalty for opening a TCP connection to a new domain. This might be why some of Google’s own sites (including web.dev) now use self-hosted fonts instead of Google Fonts.

Privacy

Paid-for web font services like Adobe Fonts need to detect page views for billing purposes, but they may be collecting more data than is strictly necessary. If you’re given the choice, load your fonts using CSS (<link rel="stylesheet">), instead of JavaScript (<script>), to minimise the amount of data the third-party is able to collect about your users.

Google Fonts doesn’t appear to collect much on website visitors beyond IP addresses and User Agent strings, but Google aren’t acting completely selflessly by providing the service for free. Each one of the fifty trillion[3] page views using Google Fonts is a data point Google wouldn’t have if the websites chose to use self-hosted fonts instead.

Control

With self-hosted fonts you have ultimate control over exactly how you load your fonts, allowing you to serve custom subsets, define font-display settings, and specify how long the browser should cache font files for.

Reliability

Third-party services can suffer slowdowns, outages, or shut down altogether. When self-hosting your fonts, as long as your website is up, your fonts will be available.

Conclusion

Each of these steps can have a benefit on its own, but used together can lead to big improvements. If you decide to implement some of the steps covered in this article, try using a tool like Lighthouse or Web Page Test before and after you make changes, to see the effect of each individual change.

In part two, we’ll cover some more advanced techniques including JavaScript font loading strategies and variable fonts. We’ll also see the importance of choosing the right fallback fonts and introduce a new acronym — FOFT, the Flash Of Faux Text.


  1. Google Fonts FAQ: What does using the Google Fonts API mean for the privacy of my users? ↩︎

  2. Say goodbye to resource-caching across sites and domains, Stefan Judis ↩︎

  3. Figure from Google Fonts Analytics, 2021-05-12 ↩︎