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:

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.