Zach’s ugly mug (his face)

Zach Leatherman

23 Minutes of Work for Better Font Loading

21 Nov 2017 Zach Leatherman
#4 popular About 700 words

Last night I hopped on Twitter after my daughter went to sleep with a little bit of time to kill before a 9 PM NEJS CONF 2018 planning meeting. What greeted me was perhaps the most tailored, best targetted content I’ve seen on the platform since Donald’s Twitter account was deactivated for a blissful 11 minutes.

8:29 PM, 31 minutes to meeting

I can do that! I can do that! Move move move move, I can do that I can do that!

8:33 PM, 27 minutes to meeting

To confirm that code changes I made would be helpful, I asked:

She replied quickly with a confirmation.

8:37 PM, 23 minutes to meeting

Work was underway. I have not worked on this project before. I didn’t know anything about the project’s structure or build process—but as luck would have it, they were all tools I’d used before. I forked the repo. I pulled the code down using git. I set up a virtual host for localhost previews. It was all happening. I did a project search for @font-face and found two: 8bit Art Sans and VT323.

Let’s make some changes

  1. Delete all of the web font files that aren’t TTF files from the project—we’re gonna make our own.
  2. Use glyphhanger to subset the fonts automatically to the code points used on the actual site (while also including ASCII). I ran:
    • glyphhanger http://make8bitart.localhost/ --US_ASCII --subset=assets/fonts/*.ttf
    • Note that glyphhanger outputs optimized subset WOFF2, WOFF (with zopfli encoding for more savings), and TTF files.
  3. Update the @font-face CSS blocks to point to the new subset files and remove all of the other formats—we’re only using WOFF2, WOFF, and TTF here. Maybe glyphhanger needs a feature to help with this step too!
  4. Add font-display: swap for full FOUT on supporting browsers (just Chrome right now but support will grow over time).
  5. preload both web fonts to make requests start earlier and reduce FOIT and FOUT:
    • <link rel="preload" href="FILE_PATH.woff2" as="font" type="font/woff2" crossorigin>
  6. Update the Service Worker to cache the WOFF2 versions only, since Service Worker is a browser support subset of WOFF2.
  7. Check it all in and make a pull request
glyphhanger command output, automatically subsetting both web fonts.

9:01 PM, -1 minute to meeting

Whew. Well—okay, so I was a little late to the meeting but the pull request was opened at 9:00 PM on the dot so I’m counting it.

Performance

(Fast 3G network throttled, Chrome)

Let’s see how the page loads before and after we made changes.

Before

  • First paint: 773ms (screenshots do not include network time)
  • Page weight: 296 KB (dang, Jenn—nice work)
  • Font weight (TTF and WOFF): 7.1 KB + 84.8KB = 91.1 KB
  • FOIT:
    • 773ms -> 1.91s for 8bit Art Sans: 1.137s total
    • 773ms -> 3.72s for VT323: 2.947s total (just under that 3s FOIT timeout window)
  • FOUT: none

After

  • First paint: 763ms (similar)
  • Page weight: 215 KB
  • Font weight (WOFF2): 4.2 KB + 6.5 KB = 10.8 KB (11% of original, savings of 80.3KB)
  • FOIT: none, font-display would have kicked in if preload hadn’t beat it to the punch.
  • FOUT:
    • none for 8bit Art Sans, preload is killing it.
    • none for VT323, preload is killing it.

Recap

Font loading can be daunting and even a little confusing at times. If you’re feeling overwhelmed by the topic, the above steps can serve as a shortlist of things you can quickly do to make an improvement to your site. It doesn’t have to be the best—just make it better!

Jenn really has a nice site here. It was simple, easy to work on, and to be fair—fast already. But for web fonts, there were a few changes that helped. We saved 80KB of web font content by subsetting. We’ve eliminated FOUT and FOIT on a Fast 3G connection using preload. If on a slower connection, we’re using font-display in browsers that support it to FOUT instead of FOIT.

Not bad for 23 minutes worth of work.

16 Comments

➡ Load Disqus to Leave a Comment ⬅

Nice work, Zach. Really digging in deep like that for performance always leads to a way better overall experience that people may not consciously appreciate—but it makes the web a better place.

Before today I wasn't aware of font-display: Swap. Appreciate the help!

I think preload is great but think you should add info on how one can feature detect support for link rel=preload

Shouldn’t need to feature detect. Fallbacks are built into the @font-face blocks.

Dear Zach,
I've been a front end developer for two years. But's i'm not so educated on fonts. So excuse me for asking!

1. subset the fonts automatically to the code points used on the actual site (while also including ASCII
→ i have no idea what this sentence means.

2. What's special about WOFF2, WOFF and TTF? You're saying to avoid OTF right?

3. What is FOIT and FOUT?

4. What is font-display: swap?

1. Subsetting is a method to reduce the number of characters stored in a font file. I use `glyphhanger` to find the characters that are used on the actual site (and throw in ASCII just to be sure).

2. If you have OTF, use it! I like OpenType features. Unfortunately the sources for this site did not have them.

3/4. This might help: https://www.zachleat.com/we...

I might be confused about this, but I thought that, in order to avoid FOIT, you need to *delay* applying the webfont to the page with JavaScript until after it loads. E.g., I have this on my website:

document.fonts.load('1em Roboto').then(() => {
document.documentElement.style.fontFamily = 'Roboto, sans-serif';
});

So, do your optimizations prevent FOIT in non-Chrome browsers as well or just in Chrome?

This is a good question. When you preload, you’re racing for the request to finish before first paint. Subsetting helps there too. So it’s certainly not foolproof, but the theme of this blog post was “quick improvement” and not something I would consider a best practice for something in a more professional setting / client work / my own web site 👀. As preload and font-display browser support broadens though, that could change.

WOW! As a digital signage company that has to load 30-40 fonts... THANKS!

Just a comprehension question: if a new team member called "Estefán Süverkrüpp" has to be listed on the website you just improved, they have to update their webfonts? (assuming ü and á were not used there before...)

Correct. See also https://www.zachleat.com/we...

You can also whitelist characters or character sets using glyphhanger.

Correct me if I'm wrong, but this method seems unusable for sites with user-generated content as you can't predict what characters from the font your users will want to use in their content. That being said, on a static site or HTML banner ad, this is a blessing. Wish I had it in my pre-enterprise days. Nice work and thanks!!!

Not correcting you—you’re not wrong 😎

I’ve had good luck with loading a subset first and then lazy loading the full font later to override! Or your design may be okay with using system fonts for user generated content (I’m okay with Georgia on these Disqus comments, for example).

Interesting technique. I definitely have run into where that could work. Thanks for the reply!

Great advise!

I tried applying that to my (german) website. Glyphhanger (nice name) does not find the german "umlauts" though. I tried --UTF8 and --LATIN1 as character sets but it seems to just ignore them. Do you have any advise on that? The documentation of glyphhanger is not very verbose on that.

the line to create the subset would be the following: glyphhanger https://samui-samui.de/ --UTF8 --verbose --subset=assets/fonts/*.ttf

It does not find the Ö. I guess I will try to implement the trick with loading the full font later on after loading a subset.

It also does not find the Y ;) very weird.

Can you add a new issue for this to https://github.com/filament...

It may be a timing issue, glyphhanger definitely works with those characters in my tests.