Zach’s ugly mug (his face) Zach Leatherman

23 Minutes of Work for Better Font Loading

November 21, 2017

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.


(Fast 3G network throttled, Chrome)

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


  • 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


  • 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.


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.

< Newer
Airing Out 2017’s Dirty Laundry
Older >
NebraskaJS Omaha

Zach Leatherman IndieWeb Avatar for a builder for the web and the creator/maintainer of IndieWeb Avatar for https://www.11ty.devEleventy (11ty), an award-winning open source site generator. At one point he became entirely too fixated on web fonts. He has given 81 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of CloudCannon, Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »


IndieWeb Avatar for https://prema.kapilaya.netIndieWeb Avatar for https://imjohnainsworth.comIndieWeb Avatar for https://www.yogitatrainingcenter.comIndieWeb Avatar for https://www.splendidwebsites.comIndieWeb Avatar for https://imaginemarketingonline.esIndieWeb Avatar for https://imaginemarketingonline.esIndieWeb Avatar for https://nice-space.comIndieWeb Avatar for http://marketingtumbler.comIndieWeb Avatar for https://wpwerk.comIndieWeb Avatar for Avatar for https://www.seowebdesignllc.comIndieWeb Avatar for https://www.smashingmagazine.comIndieWeb Avatar for https://business2020.xyzIndieWeb Avatar for http://adharidshop.liveIndieWeb Avatar for https://gsensenews.comIndieWeb Avatar for http://niclink.irIndieWeb Avatar for https://hostulum.comIndieWeb Avatar for Avatar for https://webdesign.idjgie.xyzIndieWeb Avatar for https://www.smashingmagazine.comIndieWeb Avatar for https://allprowebdesigns.comIndieWeb Avatar for https://www.webmastersgallery.comIndieWeb Avatar for https://smartphonephotography.inIndieWeb Avatar for http://unsorted.coIndieWeb Avatar for
  1. Jeff Golenski Disqus

    21 Nov 2017
    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!
  2. Curt Disqus

    27 Nov 2017
    I think preload is great but think you should add info on how one can feature detect support for link rel=preload
    1. zachleat Disqus

      27 Nov 2017
      Shouldn’t need to feature detect. Fallbacks are built into the @font-face blocks.
  3. Luca Ban Disqus

    29 Nov 2017
    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. zachleat Disqus

      30 Nov 2017
      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:
  4. Šime Vidas Disqus

    01 Dec 2017
    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(() => { = 'Roboto, sans-serif';});So, do your optimizations prevent FOIT in non-Chrome browsers as well or just in Chrome?
    1. zachleat Disqus

      01 Dec 2017
      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.
  5. Jason Maggard Disqus

    01 Dec 2017
    WOW! As a digital signage company that has to load 30-40 fonts... THANKS!
  6. someone Disqus

    02 Dec 2017
    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...)
    1. zachleat Disqus

      06 Dec 2017
      Correct. See also can also whitelist characters or character sets using glyphhanger.
  7. Lounge9 Disqus

    06 Dec 2017
    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!!!
    1. zachleat Disqus

      06 Dec 2017
      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).
      1. Lounge9 Disqus

        07 Dec 2017
        Interesting technique. I definitely have run into where that could work. Thanks for the reply!
  8. Patrick Disqus

    06 Dec 2017
    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 --UTF8 --verbose --subset=assets/fonts/*.ttfIt 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.
    1. zachleat Disqus

      06 Dec 2017
      Can you add a new issue for this to may be a timing issue, glyphhanger definitely works with those characters in my tests.
      1. Patrick Disqus

        06 Dec 2017
        done :)
Shamelessly plug your related post

These are webmentions via the IndieWeb and

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)