Advanced web font optimization techniques

Web developers have been using custom fonts for years. Since these fonts don't come installed on the operating system, they have to be loaded with the site, and therefore need to load fast and look the same across every platform. A common example is Google Web Fonts, a library of web-optimized fonts in Web Open Font Format (WOFF2), which offers excellent compression to keep their weight to a minimum.
In practice, however, a Google Web Fonts font usually weighs between 20 and 30KB. Multiply that by several styles and weights and you quickly reach 100–150KB on your site. Another downside of custom fonts is that they cause a visible layout shift while the page loads.
In this article we'll cover advanced techniques to make your website faster and deliver a better UX with web fonts.
The basics, in a moment
Before moving on to the advanced techniques, let's quickly go over the basics. Here's a checklist of good practices you can follow:
- Use Google Web Fonts whenever you can.
- If you use a custom font, try running it through Font Squirrel to compress it better and fix the vertical baseline.
- Use WOFF2 if you embed a font manually. WOFF2 has very broad support (over 97%) and compresses much better than other formats.
- Use
<link rel="preload" href="/fonts/usual/usual-semi-bold.woff2" as="font" type="font/woff2" crossorigin="anonymous">to prioritize the font in the HTTP queue. More information. - Make sure the font is cached with
cache-control: public, max-age=31536000, immutable. If you use Vercel, Netlify, or Gatsby Cloud, you likely already have good caching policies in place. - Download the font and serve it from the same CDN as your site. Again, Vercel, Netlify, and Gatsby Cloud already do this by default.
Now let's talk about advanced optimization
Here are a few things that will save you some KB, make your fonts look better, and improve UX:
- Fallback font size adjustments
- Custom subsetting
- Vertical baseline correction
Fallback font metric overrides
Using custom fonts that aren't part of the operating system comes at a cost. The browser doesn't know a font's parameters until it has loaded it; in the meantime, it uses a fallback font (for example, Arial or Times New Roman) to calculate the size of text elements on the page. But as soon as the font loads, the size gets recalculated.
The difference can be huge, and the layout shifts are noticeable, especially on a slow connection.
This example compares two fonts: Times New Roman and a custom font called Usual. Both are set at 16px with a 1.2 line height, but notice the difference in length with the same text.
A few new CSS properties have recently become widely available to match the metrics of the fallback font and the custom one.
Sounds great, but how do you match them? Katie Hempenius and Kara Erickson, from Google's Aurora team, built an algorithm for exactly that, but getting the full benefit of it requires automated tooling. The Next.js team recently announced that it's baking this into the updated @next/font package in v13. Fontpie follows the same approach: it's a CLI that wraps this algorithm and generates the CSS needed to adjust the fallback font's metrics.
It's very easy to use. Here's an example command:
npx fontpie ./roboto-regular.woff2 --name Roboto
Running it gives you the following CSS, which you can include in your project regardless of the framework or language you're using:
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: swap; src: url('roboto-regular.woff2') format('woff2');}@font-face { font-family: 'Roboto Fallback'; font-style: normal; font-weight: 400; src: local('Times New Roman'); ascent-override: 84.57%; descent-override: 22.25%; line-gap-override: 0%; size-adjust: 109.71%;}html { font-family: 'Roboto', 'Roboto Fallback';}
This CSS solution works like this: (1) we declare a new font face, Roboto; (2) then we declare another font face called "Roboto Fallback" that uses the local Times New Roman font with the metric overrides applied. This completely eliminates the layout shift.
As you can see in the image, Times New Roman with the metric overrides now takes up the same space as the Usual font.
This technique is supported in every modern browser except Safari.
Custom subsetting
This technique reduces the number of characters included in a font. Fonts usually ship with far more characters than your project needs, so using a subset with only what's essential can significantly cut down its weight.
Google Web Fonts has a very handy API for creating font subsets. As an example, let's take the Space Grotesk font. Its Regular (400) variant weighs 15KB in WOFF2 format. The embed link looks like this
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk&display=swap" rel="stylesheet" />
To define a subset, you just need to add the &text parameter to the URL; for example:
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk&display=swap&text=HelloWorld" rel="stylesheet"/>
This is especially useful when you have a single "styled" headline in a font different from the rest of the site. In that case, you can define a subset that only includes the characters in that headline. In the example above, the custom subset brought the font down to 1KB.
The screenshot below shows how characters look when a letter isn't in the subset.
Below are some common subsets you can copy and paste into Google Web Fonts or Font Squirrel to get exactly the character set you want to use.
Common subsets: copy and adapt them to what you need
Lowercase
abcdefghijklmnopqrstuvwxyz
Uppercase
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Numbers
0123456789
Accented uppercase
ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞߌŸ
Accented lowercase
àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿıœƒ
Alternative punctuation
¡«»¿‚„‹›
Mathematical symbols
ª¬±µº÷Ωπ‰⁄∂∆∏∑√∞∫≈≠≤≥
Typographic marks
§©®°¶·†‡•™◊fifl
Currency
$¢£¥ƒ€
Punctuation
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~–—‘’“”…
Diacritics
¨¯´¸ˆˇ˘˙˚˛˜˝
For example, the Space Grotesk subset defined below includes uppercase, lowercase, numbers, punctuation, and currency, and the resulting font weighs only 6KB.
https://fonts.googleapis.com/css2?family=Space+Grotesk&display=swap&text=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!%22#$%&'()*+,-./:;%3C=%3E?@[\]^_%60{|}~%E2%80%93%E2%80%94%E2%80%98%E2%80%99%E2%80%9C%E2%80%9D%E2%80%A6$
Custom font subsets
Things get worse if you use custom fonts, like those from Adobe Fonts. Many of them aren't optimized for the web and contain a ton of unnecessary characters, so they can easily reach 30KB. Font Squirrel lets you create custom subsets without any hassle: upload your font in TTF or OTF format and enable Expert mode along with custom subsetting.
Creating subsets is easy: check the ones you want to keep, or type a specific character into the Single Characters field. For example, the copyright symbol is in the "Typographics" subset, but if you only need ©, add it to the Single Characters field.
Vertical baseline correction
Fixing fonts with Font Squirrel
Another possible issue when using custom, or poorly optimized, fonts is an incorrect vertical baseline. For example:
These fonts can cause inconsistent spacing like the one above, where the top padding is smaller than the bottom padding even though the CSS sets the same value for both. Sometimes, simply running the font through Font Squirrel fixes the problem automatically.
If that doesn't work, you can fix it with Custom Adjustments. Finding the right combination of metrics takes some time, but it solves the problem.
Using Capsize
Capsize is a library that uses the font's metadata to size text based on its cap height, trimming the space above the capitals and below the baseline. It applies CSS rules using the :before and :after pseudo-elements, adjusting margin-top and margin-bottom so the text fits perfectly inside its box and fills the available space.
Example result:
// For 48px.css-dpa7xb::before { content: ''; margin-bottom: -0.1432em; display: table;}.css-dpa7xb::after { content: ''; margin-top: -0.2142em; display: table;}// For 24px.css-1m2jnlz::before { content: ''; margin-bottom: -0.322em; display: table;}.css-1m2jnlz::after { content: ''; margin-top: -0.393em; display: table;}
It's an extremely easy solution to use and it always works. It only requires extra CSS for each font size you use on the page. Even so, I'd recommend trying Font Squirrel's automatic corrections first and reaching for Capsize only if needed.
Summary
Let's recap what we covered in this article:
- Adjusting the fallback font's metrics is a quick and simple fix you can apply to any project with a dedicated font metrics tool or with
next/font. - Remove font subsets you don't need, especially if you're using a custom, styled font in the Hero section purely for design purposes.
- Keep an eye on the font's vertical baseline and try to fix it if you notice you need different top and bottom padding on a button to make the text look centered.