Skip to main content

Optimizing Webfont Performance

Webfonts play a crucial role in giving our websites personalized feel, but they can also be a hidden culprit behind sluggish load times. If you’ve ever wondered why your beautifully designed site isn’t performing optimally, inefficient font loading strategy might be to blame. In this article, we’ll delve into practical strategies to enhance your webfont performance, ensuring a better user experience.

Back in January, when I was working on the website’s design and setting up the foundations for it, I eventually settled on a specific typeface called Brandon Text from the HvD Fonts type foundry. After purchasing the license and downloading the webfonts, I quickly became aware that the webfonts would in their (then) current form be the single biggest resource downloaded over the network.

For context, I’m using four different styles of Brandon Text: Regular, Regular Italic, Bold, and Black.

Ensuring Text Visibility During Loading

The first thing that you’d usually do to improve webfont performance is to ensure that your website’s text remains visible during font loading. To achieve this, I’m using font-display:swap in the @font-face rule in CSS:

@font-face {
  font-family: "brandon";
  src: url("/fonts/brandon.woff2") format("woff2");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}

The above ensures that the user can still read the content using a fallback font if the webfont does not load or takes a long time to load.

This approach creates another problem though, which is layout shift caused by deferred fonts. Basically, you may now at times temporarily get a system font and experience a flash of unstyled text (FOUT).

To combat this, I ended up defining a local @font-face rule as well with size-adjust and ascent-override to more closely match the fallback font and the actual webfont that I wanted to be displayed:

/* Make a custom fallback font based on the local Arial */
@font-face {
  font-family: "brandon-fallback";
  size-adjust: 93%;
  ascent-override: 117%;
  src: local("Arial");
}

Then, in my font-family declaration, I’m defining it like so:

:root {
   --font-family: "brandon", "brandon-fallback", sans-serif;
}

/* …Later in another CSS partial: */
body {
  font-family: var(--font-family);
}

Preloading Webfonts

To further improve things, I also wanted to make sure that the webfonts are being preloaded for optimal loading speed to minimize the flash of unstyled text. To achieve this, I added the following link elements to the <head> section of the website:

<link rel="preload" href="/fonts/brandon.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/brandon-italic.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/brandon-black.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/brandon-bold.woff2" as="font" type="font/woff2" crossorigin />

Configuring Caching

I also wanted to make sure that the webfonts would get served through the content delivery network using optimal caching. I’m using Netlify to host this website, so to tweak this, I utilized the netlify.toml configuration and added the following:

[[headers]]
  for = "*.woff2"
  [headers.values]
    Cache-Control = "max-age=31536000,public,must-revalidate"

Offline Support

Since my website is also a progressive web app, I wanted to make sure that the webfonts would stay functional in case the network connection fails. So I added the following into the website’s service worker:

function updateStaticCache() {
  return caches.open(staticCacheName).then(staticCache => {
    return staticCache.addAll([
      "/fonts/brandon.woff2",
      "/fonts/brandon-italic.woff2",
      "/fonts/brandon-black.woff2",
      "/fonts/brandon-bold.woff2"
    ]);
  });
}

Subsetting Webfonts

While these steps improved the overall webfont performance, they still left me with the problem that the webfonts were the single biggest resource being downloaded over the network on the first visit.

Being conscious about webfont licensing and the limitations they usually have, I reached out to the type foundry directly to see if they’d allow me to subset the webfonts:

Screenshot of me nerding about the webfonts in an email to the type foundry after I got their permission to subset the fonts. Me nerding about the webfonts in an email to the type foundry. ❤️

Once I had their permission to subset the webfonts, I ended up building an automation to do just that in the build phase. I made it work so that the source code still contains the original font files, but only subsets of them get deployed to the CDN. This way, I now always have the right subset of characters needed to serve this website’s content, but nothing extra.

To achieve this, I used Glyphanger and made this process a part of the build script. The way Glyphanger works is that it analyzes the pages on this website to find the necessary glyphs, and then subsets the original fonts using the unicode-ranges it finds.

While I won’t go into detail of the whole automated build process in this post, I still wanted to provide the manual steps for anyone wanting to do something similar. To get started, you should first install Glyphanger:

npm install -g glyphhanger

Then, run the following command to install the necessary Python dependencies (tested on the latest macOS):

pip3 install fonttools brotli zopfli

Once you’ve installed the dependencies, run the below command to get the glyphs. This will give you the unicode-range used:

glyphhanger https://arielsalminen.com

You can also specify multiple URLs and they can be local too:

glyphhanger https://arielsalminen.com https://arielsalminen.com/about/

Finally, you can use the returned unicode-range with the --whitelist option to subset the webfonts:

glyphhanger --whitelist=U+20,U+61-7A --formats=woff2,woff --subset=brandon.otf

The --formats option in the above script defines which formats Glyphanger should output. The --subset option refers to the original font file that you want to subset.

The Outcome

I’m quite happy with this solution since the website source still contains the original font files, but with the subsetting, I managed to get the weight down from 350.3KB to just 147.7KB. A reduction of 202.6KB on the data transferred over the network on the first visit.

Written by Ariel Salminen.

Ariel Salminen.

Get in Touch

Does your team need help? I’m Ariel Salminen, a Design Systems Architect specialized in helping organizations build and maintain design systems. I’m available for both local and remote projects around the world.

Keyboard Shortcuts