Three Techniques for Performant Custom Font Usage

Avatar of Ollie Williams
Ollie Williams on

There’s a lot of good news in the world of web fonts!

  1. The forthcoming version of Microsoft Edge will finally implement unicode-range, the last modern browser to do so.
  2. Preload and font-display are landing in Safari and Firefox.
  3. Variable fonts are shipping everywhere.

Using custom fonts in a performant way is becoming far easier. Let’s take a peek at some things we can do when using custom fonts to make sure we’re being as performant as we can be.

1) Reduce the File Size

Far from containing only numbers, the Latin alphabet and common punctuation, many fonts support multiple languages and include thousands of additional glyphs, adding significantly to the file size.

The Mac tool Font Book can display all the available glyphs in a font.

In these cases, subsetting is useful. Subsetting is the removal of characters you don’t need. You can do this using the command line tool pyftsubset, which can be downloaded as part of FontTools. (The website FontSquirrel offers a GUI for subsetting. Its a popular alternative thats also worth considering – but be aware that it isn’t compatible with variable fonts). The easiest way to specify the characters you want to keep is with unicode. Unicode is a standard that provides a unique code to every character from the world’s languages.

Unicode has assigned a number to many thousands of characters. We are interested in a very limited subset.

The pyftsubset command is followed by the name of your font file and the unicode specifying your chosen unicode range. The following is a subset that caters to the English language.

pyftsubset SourceSansPro.ttf --unicodes="U+0020-007F"

You can then reduce the size of the font further by compressing to a woff2 file format. You could do this with a separate command line utility from Google, or just add some extra options when you run the pyftsubset command:

pyftsubset SourceSansPro.ttf --unicodes="U+0020-007F" --flavor="woff2" --output-file="SourceSansPro.woff2"

If you’re confident you’re not going to use any of the characters you’ve removed from the font, this is all you need to do — you’ve radically lowered the size of the font, and you can now use it in @font-face as usual. But what if some of the pages on your site make use of additional glyphs that have been removed from the font? You’ll want to avoid any characters being displayed with a fallback typeface. To solve this potential issue, be sure to note the range you use when creating your subset with pyftsubset. You’ll want to specify it within the @font-face block.

@font-face {
  font-family: 'Source Sans Pro';
  src: url('/fonts/SourceSansPro.woff2') format('woff2');
  unicode-range: U+0020-007F; /* The bare minimum for the English Language */

The above unicode-range is specifying characters that are likely to be used across all pages of the site. This font will therefore be downloaded on every page.

Firefox has the best font-related dev-tooling. Look in the Fonts tab of Firefox to find out which fonts are being used. In other browsers look in the Network tab to find out which font files are being downloaded.

I’ll also create another file with pyftsubset. This won’t repeat any of the characters from the the first file. Rather, it will only contain glyphs that are rarely used on the site.

pyftsubset SourceSansPro.ttf --unicodes="U+00A0-00FF,U+0100-017F" --output-file="SourceSansProExtra.ttf"

By again specifying a unicode-range within another @font-face declaration, we ensure the file will be downloaded only when its needed — when a character on the page matches against the specified range.

@font-face {
  font-family: 'Source Sans Pro';
  src: url('/fonts/SourceSansProExtra.woff2') format('woff2');
  unicode-range: U+00A0-00FF, U+0100-017F; /* additional glyphs */
Adding some random French accents to the HTML will cause the extra subset file to be downloaded.

You should cater the range to your own purposes. If you have any unusual punctuation or letters in your header or footer, make sure you include them in your main font file, otherwise both files will always be downloaded. For the site I work on, for example, I’ve included the © symbol (U+00A) into the main subset, because it’s included at the bottom of every page. You can find a handy list of unicode characters on Wikipedia.

2) Load Key Font Files as Early as Possible

Imagine the only CSS in your stylesheet is one hundred @font-face declarations and the following lines of code:

* {
  font-family: 'Lora';
  font-style: italic;
  font-weight: bold;

How many fonts would the browser download? One hundred? The answer is,
just one. Fonts are lazy loaded by default. This is a smart move by browser vendors — only download what is actually needed. There is, however, a downside. To know what font variants are being used, the browser must parse all the HTML and all the CSS. Only after all of that will any font files be requested. If we want to kick things off more quickly, we have to use preloading.

Preloading is as simple as adding a link tag into the head of the document:

<link rel="preload" href="/fonts/Lora-Regular.woff2" as="font" type="font/woff2" crossorigin>
The preloaded font is the first resource to download after the HTML document itself.

It’s not a good idea to preload too many resources, or wastefully preload any resource that might not be used on the page. For this reason I stick to preloading only the normal weight, non-italic file. I’m less concerned about a late-loading bold or italic file as those styles are used less often across the site. A flash of unstyled text (FOUT) for these font files is less disruptive than a FOUT of the regular Roman font, which makes up the bulk of the text. If you’re using a variable font, this is a non-issue. However, preloading variable fonts currently presents its own problem.

Should You Preload a Variable Font?

It’s clearly wasteful to send a browser a resource it can’t use. That’s why we include a type attribute that specifies the MIME type of the resource. If the resource isn’t supported by the browser, it won’t be downloaded. In the case of fonts, we specify type="font/woff2". Edge, Safari and Firefox are implementing both variable fonts and preload at the same time, so there isn’t an issue for those browsers. Chrome and Opera, however, have supported preload since 2016. Variable fonts are a brand new addition, whereas woff2 has been supported since 2014. For that reason, preloading a variable woff2 will force older versions of Chrome and Opera to download a resource they won’t know what to do with.

Ideally, we’d be able to specify the resource as a variable font within the type attribute, but this is largely unnecessary. Chrome is evergreen. There’s a new release every six weeks and it automatically updates in the background. Relatively few visitors will be using an ancient version. Once variable fonts have been around in Chrome for several versions, it will be safe to preload.

3) Managing FOUT and FOIT

So far we’ve looked at speeding up font loading. A good user experience is not just about the total loading time of a page or a font — it’s about how users experience the site while it’s loading. If you want users to stick around, you need to think about perceived performance. The different browsers aren’t consistent in how they manage font loading. We can now easily decide the loading strategy for ourselves using the CSS font-display property.

The font-display property is defined within an @font-face block.

@font-face {
  font-family: ExampleFont;
  src: url(/fonts/SourceSansPro.woff2) format('woff2');
  font-display: fallback;

The available options are block, optional, swap and fallback. Which should you pick?

The optional value sounds like a good bet — fonts are a nice-to-have enhancement, but not a strict necessity. In practice, I’ve found it a bad choice. Seeing the typeface of a page change upon navigation or reload is unexpected for users, and somewhat disconcerting. The window of time given for the custom font to load is incredibly small. If you’re preloading the font, it’s likely to load within this narrow timeframe. If not, users will often experience the fallback web safe font — even on a good connection with an expensive computer. That’s not great for brand consistency. If you’ve put much time into finding a similar fallback font, this might be an acceptable option, but if it’s that similar and performance is paramount, then maybe you should reconsider why you’re using a custom font at all.

The swap value is a better option. The web safe fallback font is shown immediately and replaced by the custom font once it’s loaded. Unfortunately, the swap period is infinite, so on a slow connection, the font could change after the user has already become immersed in the text, creating a jarring and disconcerting experience.

The fallback value is similar to the default behavior of most browsers — a short block period of invisible text followed by a fallback font if the custom font still hasn’t loaded. Unlike swap, there is a timeout period. If the custom font doesn’t load within three seconds, the font doesn’t change — the user is left with the fallback. You can read a whole article on CSS-Tricks about font-display and decide which option works best for you.

Wrapping Up

Text makes up the majority of content on most websites. Font-loading is therefore an important piece in the performance puzzle. If you want to keep up with font loading developments, Zach Leatherman just started a newletter about the subject.