Hello, fellow web perf enthusiast! Contribute to the 2024 edition happening this December. Click.

Web Performance Calendar

The speed geek's favorite time of year
2017 Edition
ABOUT THE AUTHOR

Jeremy Wagner (@malchata) is a professional front end developer living in the frozen wastes of Saint Paul, Minnesota. He is the author of Web Performance in Action from Manning Publications. You can see what he's up to on his blog, or on Twitter @malchata.

I guess web fonts are a big thing these days. Most of us know how know to write @font-face declarations by now. You know all about WOFF and WOFF2, possibly a bit about subsetting, or maybe even font-display for more accessible rendering of text. Hell, maybe you just grab a <link> tag from Google Fonts and go. Whatever the case may be, here’s three tips for faster fonts you may not have encountered yet.

Host fonts on your own server

Most of us grab a Google Fonts <link> snippet and go. To be fair, that’s what I did for quite some time, but my default position on loading third party resources has since shifted to one of mindfulness. Sure, third party resources benefit from CDNs, which minimize latency for the end user. That’s great and all, but I could just as easily put all of my site’s assets behind a CDN, and host them myself.

The one benefit of using a third party service that I can think of is that cache hits are more likely to occur, as your browser cache may contain the same asset used by another origin. If your site uses an ubiquitous font family like Droid Sans or Open Sans, you might benefit from the cache hits a third party service could afford. Potential caching benefits aside, there’s a lot of good hosting your own fonts can do:

  1. Hosting your own fonts translates to less origins for the browser to resolve and subsequently connect to. Less servers == less connections == less latency overall.
  2. Third party services confer less control over the content of a @font-face declaration, because the CSS is controlled and hosted by the third party. True, services like Google Fonts mitigate this somewhat through query string parameters that allow you to make indirect modifications to the CSS they provide. But not having total control over your @font-faces means you’re going to have less flexibility.
  3. A third party may only give you coarse control over font subsets. While any amount of subsetting is better than none at all, your site’s design and the frequency with which it uses certain typefaces may gave you opportunities to be aggressive with your font subsetting. Although Google Fonts in particular does have a workaround for this, which is covered later on.

In spite of the above benefits, there may be times when your organization’s development best practices mandate the use of a third party provider. Or time is a limiting factor. Or EULAs don’t permit you to host your own fonts. If that describes your situation, you should expend a bit of effort mitigating potential latency. Say you have a typical Google Font embed code in your <head>. You could minimize the amount of time it takes to connect with that server using the preconnect resource hint:

<link rel="preconnect" href="https://fonts.googleapis.com/" crossorigin="anonymous">
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin="anonymous">

A more widely compatible alternative to preconnect is dns-prefetch. It won’t establish a connection to the server, but it will resolve the DNS for the specified host, which can still speed things up a bit:

<link rel="dns-prefetch" href="https://fonts.googleapis.com/">
<link rel="dns-prefetch" href="https://fonts.gstatic.com/">

Either way you do it, these little optimizations could help take the sting out of opening a connection to a third party when hosting your own fonts is out of the question.

Aggressively subset fonts

When we subset fonts, we often do so by unicode ranges that encompass entire lettering systems. Latin, Latin Extended, Cyrillic, and et cetera. What if you make extremely limited use of a typeface in a design, though? Particularly in design elements that rarely (if ever) change? Here’s a case of just that on my blog:

An example of limited use of a typeface.

This is the header on my blog. The font family used for the “WEB DEV BLOG” text is Monoton, and this copy in the site header is the only place that this font is ever used. This is the exact sort of design element would benefit from aggressive subsetting: It never changes, and uses a tiny fraction of the glyphs the font has to offer.

“Why not just replace it with an image?” is a counter argument I anticipate in response to this strategy. That’s a potential option with (assuming reasonable alt text is part of the solution), but I would much rather code this design element as a text node so robots/crawlers/screen readers can grok it as easily as possible.

On the other hand, if I embed the Latin subset of Monoton using the <link> tag provided by Google Fonts, I’m loading a lot of characters I’m not likely to ever use. The table below illustrates what each format of the Latin subset of Monoton as provided by Google Fonts weighs:

Format Size Compressed Size*
WOFF2 16.02 KB n/a
WOFF 20.75 KB n/a
Embedded OpenType 38.74 KB 20.57 KB
TrueType 38.57 KB 20.5 KB

*Using gzip at the default compression level setting of 6.

In the best case scenario, I’m asking users to load a WOFF2 font file that’s 16 KB for a design element that only uses nine unique glyphs. Surely we can do better if we aggressively subset that font to those glyphs. But how? That part is pretty easy if you use glyphhanger, a command line utility by The Filament Group, plus a Python utility called pyftsubset (distributed with fonttools):

pyftsubset monoton.ttf \
  --unicodes=$(glyphhanger ./chars.txt)\
  --name-IDs='*'\
  --output-file=monoton-subset.ttf

In this example, we use pyftsubset to subset monoton.ttf to monoton-subset.ttf. The unicode ranges are gathered by running glyphhanger within a subcommand that reads the content of chars.txt. The content of chars.txt in my applied example is “WEB DEV BLOG”. When glyphhanger finishes, we’re left with a new TrueType font subset that’s super lean at 3.36 KB. In this case, that’s 10% of the original Latin subset.

Of course, a TrueType font is only one ingredient of a complete @font-face recipe. You’ll need a few other formats to fill everything out. To do that, you can use the ttf2eot, ttf2woff and ttf2woff2 Node CLI programs thusly:

ttf2eot monoton-subset.ttf monoton-subset.eot && \
ttf2woff monoton-subset.ttf monoton-subset.woff && \
cat monoton-subset.ttf | ttf2woff2 >> monoton-subset.woff2

Which yields the following asset sizes:

Format Size Compressed Size*
WOFF2 1.86 KB n/a
WOFF 2.55 KB n/a
Embedded OpenType 3.52 KB 2.28 KB
TrueType 3.36 KB 2.23 KB

*Using gzip at the default compression level setting of 6.

Of course, you could use glyphhanger to analyze your entire site, and subset all fonts aggressively based on page content. subfont is another such offering that makes a solid attempt at this problem. I’m currently working on version 2 of unicode-ranger, which uses Puppeteer to analyze page contents with a reasonable degree of accuracy. Although I would warn that unicode-ranger is a work in progress, and isn’t as mature as glyphhanger and subfont. If you decide to use unicode-ranger and encounter problems, please file an issue!

Again, if you can’t self host your own font files, generating tiny font subsets might not be an option for you, but there are ways to get there. For example, Google Fonts has a feature that allows you to do dynamic subsetting if you supply text via the text query string parameter in your google fonts <link> tag. If I wanted to use Google Fonts to dynamically subset the Monoton font the same way I did in the above example, I would just embed the following:

<link rel="stylesheet" href="fonts.googleapis.com/css?family=Monoton&text=WEB%20DEV%20BLOG">

Word to the wise: Don’t forget to URL encode the value you pass via the text parameter!

Cull “nice to have” typefaces in the presence of Save-Data

Here’s a fun tip that you might not know about yet, which involves sniffing for the Save-Data HTTP request header. Save-Data is sent by select browsers when users have specified in their preferences that they would prefer applications deliver a lighter experience. This feature isn’t available on many browsers beyond Chrome on Android, but those users alone represent a significant slice of all web users. If you want to get intimately familiar with how this header works and what you can do with it, you can check out this article by Ilya Grigorik, or this post of mine on CSS-Tricks.

One commenter on my CSS-Tricks article, Å ime Vidas, offered an excellent suggestion of culling “nice to have” typefaces when the Save-Data header is present. Let’s look at an example of how you might accomplish this task. To start, you could use a back end language such as PHP to check for the header:

<?php $saveData = isset($_SERVER["HTTP_SAVE_DATA"]); ?>

This code checks if the Save-Data header is set via the isset function. After we save the result of this check to a variable, we can use it to conditionally add a no-save-data class on the <body> element like so:

<body class="<?php if($saveData === false) : echo(" no-save-data"); endif; ?>">

With this logic in place, the server will send markup with a class of no-save-data on the <body> element when Save-Data is not present. We can then write styles that opt into custom typefaces when the no-save-data class is present:

p {
  font-family: sans-serif;
}

.no-save-data p {
  font-family: "Open Sans", sans-serif;
}

With this CSS, the browser won’t load fonts specified in a @font-face if a font or font-family property doesn’t ultimately call for it after the style cascade has been determined. In this example, the <p> element uses the generic sans-serif typeface by default. If the browser doesn’t send a Save-Data header (and subsequently adds a no-save-data class to the <body> element per the PHP code above), then it will opt into using Open Sans. Below is an example of this in action:

An example of serving less typefaces in the presence of the `Save-Data` header.

A slightly more straightforward solution not involving the use of CSS classes could involve inlining @font-face declarations in <style> tags and omitting a portion (or all) of them if Save-Data is present. To be perfectly clear, I’m not referring to inlining font files as data URIs, only the @font-faces themselves.

That about wraps it up. Hopefully you find at least one of these little tips useful. If you found some opportunities to speed up your font asset delivery as a result of this little article, then I consider writing this article time well spent. Happy coding, and may you avoid Comic Sans forever (unless you are using it ironically, of course).

Many thanks to Zach Leatherman, who tech proofed this piece. Check him out on the web at https://www.zachleat.com/web and on Twitter @zachleat