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:
- Hosting your own fonts translates to less origins for the browser to resolve and subsequently connect to. Less servers == less connections == less latency overall.
- 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-face
s means you’re going to have less flexibility. - 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:
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:
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-face
s 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