Asking for a Friend: Making Font Files Performance Fly

Dear reader, you are a person of culture, you know the in-and-outs of front-end web development like the back of you hand. But, face it, not everyone is as well informed as you! Perhaps you have an acquaintance who is trying to become more well read on these things, self improvement is commendable, but - gosh - you simple don't have the time to learn them in the ways, teach them to walk the path of proper front-end development. Well friend, we have a bit of time, send them over and let's begin our discussion of todays topic: Font loading!

The Problem

First, and I apologize, but I have to start with a bit of jargon:

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

Now, we don't want either of these to happen - but - well, when using a custom font they probably will, at least until that font has loaded into your cache. And minimizing that flash is what this article is all about! Now, let's too at some strategies!

Self-host your own fonts

Why you want to self-host your fonts:

Font hosting sites like Adobe or Google Fonts are quite popular for reasons we will go into later, but not to using a hosted service and self-host can be quite advantageous:

  1. 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.

  2. 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.

  3. 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.

  4. 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 minimize the amount of data the third-party is able to collect about your users.

Okay, there are two good reasons why you might want to use a hosted service like Google Fonts or Adobe Fonts:

  1. Cost
    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. Convenience
    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.

Preload your font files

To minimize 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="/someTypeFace.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 behavior, 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 prioritize those fonts which appear ‘above the fold’ (the first 100vh the user sees without scrolling).

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: 'someTypeFace';
    src: url('someTypeFace.woff2') format('woff2'),
         url('someTypeFace.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 can 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 license permits this.

Use the font-display descriptor

Neither of FOIT or FOUT are desirable, but if you’re using web fonts, one of them is probably going to happen the first time a user visits your website. 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: 'someTypeFace';
    src: url('someTypeFace.woff2') format('woff2'),
         url('someTypeFace.woff') format('woff');
    font-display: swap;
}

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

font-display: swap

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 to prevent a big layout shift when the fonts are swapped.

font-display: block

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.

font-display: fallback

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.

font-display: optional

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. For full control over font loading, you’ll need to look into JavaScript solutions.

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 file-size 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 ‘romanized’) 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.

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.

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