Subsetting my font files reduced their size by more than 60%

A deep-dive into my site's font loading strategy, and how I massively reduced the size of my font files.

Despite often describing myself as a "typography nerd", there's not all that much evidence of my font-nerdery on these pages. And given that I'm a vocal proponent of making sites small and fast, it's doubly damning that I've never given much thought to the way webfonts are applied to my own site.

I've recently finished reading two books that have reignited my interest in typography; Shift Happens by Marcin Wichary, and Universal Principles of Typography by Elliot Jay Stocks.

1. Shift Happens

Marcin Wichary's epic book about typewriters, Shift Happens, came with a digital copy of Gorton Perfected (view the specimen PDF), a re-jiggering of the classic Gorton typeface used on typewriter key caps and all over miscellaneous vintage hardware.

I used Gorton to typeset a data visualisation that I posted on r/dataisbeautiful, and we all agreed the typeface looked awesome. Ever the magpie, I started to dream about what this website would look like if I switched to Gorton Perfected.

2. Universal Principles of Typography

Reading Elliot Jay Stocks' excellent new book, Universal Principles of Typography, reminded me that variable fonts were a thing that I'd been meaning to try for years but had still not gotten around to.

Variable fonts allow you to change aspects of a font dynamically. For example, you could have one single font file that has a variable axis for weight. By changing the weight variable, you change the display weight of the font (i.e. how light or bold the font appears)

For several years I've been using multiple font files to handle the different font styles on this site: "normal", "normal italic", "heavy" (a.k.a. bold), and heavy italic were each being provided via a separate font file.

Time to get to work

Gorton Perfected has a variable axis for "weight", so switching to Gorton could potentially not only breathe new life into my site's look-and-feel, but could also bring a much-needed performance boost (by reducing the number of font files a browser would need to download to correctly render my site).

Now it was time to seriously look at changing things up.

This site's typographic timeline

2013 - 2016: Futura

Something of an obvious choice, perhaps, but one I thought long and hard about. I used Typekit (now known as Adobe Fonts) to provide the webfonts, essentially as a "Fonts as a Service" deal.

Nov 2016 - 2019: Brandon Grotesque and FF Tisa

I think of this as my site's "fancy era" (check it out on the way-back-machine) and thus needed suitably fancy typefaces. Brandon Grotesque was a huge upgrade on Futura for the headings, and FF Tisa made for a beautiful accompaniment on all the body copy. In fact, I'm half temped to revisit this pairing as it worked so well. Both were provided, again, through a Typekit subscription.

Jan 2019 - current: Cartograph

In 2019 I'd been spending way too much time on https://danluu.com/ and stripped my site's styling way back. As a CSS addict I couldn't bring myself to remove all styles, so what I settled on was what you see today (as of May 2024). The design has barely changed at all in the intervening years.

This is when I ditched Typekit and bought a standalone license for Cartograph by Connary Fagen. I still love this font, and use it in as many places as I can. Having lost the convenience of Typekit's FaaS hosting, this is when I switched to self-hosting the font files myself.

@font-face {
    font-family: "Cartograph Sans";
    src: url("/fonts/CartographSansCF-Medium.eot");
    src: url("/fonts/CartographSansCF-Medium.eot?#iefix")
            format("embedded-opentype"),
        url("/fonts/CartographSansCF-Medium.woff") format("woff"),
        url("/fonts/CartographSansCF-Medium.ttf") format("truetype");
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

/* Repeat for all font files */

:root {
    --type-family--text: "Cartograph Sans", "ff-tisa-web-pro", georgia, serif;
    --type-family--code: "Cartograph Mono", Menlo, Monaco, "Andale Mono", "Lucida Console", "Courier New", monospace;
    --type-weight--book: 400;
    --type-weight--bold: 700;
}

That's a lot of font files!

To be able to cover all the scenarios that I regularly use I need five separate font files, and each comes at a cost.

Cartograph .woff sizes:

Font variantWeightSize
SansMedium37.8 kB
SansMediumItalic42.0 kB
SansHeavy38.9 kB
SansHeavyItalic42.8 kB
MonoMedium35.4 kB

Combined download "cost" for all font files: 196.9 kB

For context, my core CSS stylesheet (app.css) is 73 kB and my loaded-on-every-page core JavaScript file is 9.8 kB. So while those font-file sizes may not seem to egregious in isolation, when viewed as a percentage of my total page weight they are a significant burden. For a JS-light page (such as this blog post), the fonts are more than 50% of the total payload a reader has to download.

Can variable fonts help me?

Spoiler alert: maybe in the future, but not right now.

One of the inciting incidents that sent me down this rabbit hole was the idea that Gorton might make a good choice for this site. I tried it, and wasn't in love with the results for a few reasons.

  • Gorton looks awesome when setting small caps (or just generously-letterspaced all-cap headlines). For long sections of body copy it's not quite so elegant.
  • Gorton has no italic. While there's a variable axis for weight, there's no equivalent for skew. This one is a deal breaker. My writing style needs italics.

So how about a different typeface, one with a variable weight and italics? This is where the "maybe in the future" comes from.

I'm super picky about typefaces, and there aren't many that I'd be happy with for something as important (to me) as my own website. And I love Cartograph, so whatever replaces it had better be absolutely amazing and an upgrade on all fronts.

I've come this far. Now I need to fix something

Despite deciding the now is not the time to change to a different typeface, I still had work to do. When it comes to webfont sizes I've been burying my head in the sand for too long, but now I've actually looked up the cold hard numbers I have to fix it.

While variable fonts might be a dead-end (for now), I do still have some options. I've head about something called "subsetting" before, but never properly looked into it. So with that in mind I dug up an old Zach Leatherman post from my bookmarks: 23 minutes of work for better font loading. In that post Zach walks through the steps required to subset a webfont and improve how the fonts are loaded.

What is font subsetting?

Font subsetting is the process of removing all the characters from a font file that you don't need. If you're only using the Latin alphabet, for example, you can remove all the other characters from the font file and save a lot of space.

To subset a font you need two things: the original .ttf font file, and a list of the characters you want to keep.

TrueType Font (TTF) files are the standard font file format, and can be converted to other formats (like .woff and .woff2) for use on the web. If you've got the .ttf you can create all the other formats you need.

How can you easily subset a font file?

Subsetting is one of those things that I've know about for a long time but always put off because it sounded so complicated. Editing raw font files sounds complex, and how do I even work out which characters I need? Maybe I could do it for a headings-only font, but for body copy? That's a lot of characters to work through.

Thankfully the aforementioned Zach has a handy little tool called glyphhanger that can help with this. Give glyphhanger a font file and some html pages, and it will work out which characters you need to keep and then generate the new files for you. It even has a built-in spider that can follow links on your site and check all the pages for font usage.

Installing glyphhanger

Glyphhanger can be installed as a node package (npm install glyphhanger), but it also requires a few other non-node things to be installed too: fonttools, brotli, and zopfli. The docs say to use pip install fonttools which in my experience never works on modern Macs without a lot of fiddling. I guess the best option would be pip3 install fonttools for modern Mac OS, but I used Homebrew for all three and had no issues (brew install fonttools etc.).

The I ran this command:

glyphhanger http://localhost:1337/archive/
    --US_ASCII
    --subset=src/public/fonts/raws/*.ttf
    --output=src/public/fonts/subsets
    --spider
    --jsdom

There's a lot going on in that command so let's break it down:

  • http://localhost:1337/archive/ is the URL of the site I want to subset the fonts for. I'm running a local server on port 1337 to serve the site, which glyphhanger can read without any trouble. I've linked to the archive page as it contains links to every article I've published (see the --spider option later).
  • --US_ASCII is the character set I want to include in the subset. My post archive probably contains all the characters I need, but I do have other non-post pages, so including the basic ASCII set covers most of my bases.
  • --subset=src/public/fonts/raws/*.ttf is the path to the raw font files I want to subset.
  • --output=src/public/fonts/subsets is the path to save the subsetted font files.
  • --spider tells glyphhanger to follow links on the site and check them for font usage. This means by using the /archive URL glyphhanger can quickly check all the important pages on my site for font usage.
  • --jsdom tells glyphhanger to use JSDOM to render the page and check for font usage. The default mode uses Puppeteer/headless Chrome, but given that my site is static (and therefore just plain HTML files and not JS-generated content) I can skip this and the command will run much faster.

The results

The size stats for the subset woff files look much better:

Font variantWeightSize
SansMedium16.7 kB
SansMediumItalic18.6 kB
SansHeavy17.0 kB
SansHeavyItalic18.8 kB
MonoMedium15.8 kB

And what's more, glyphhanger also generates woff2 files, which are even smaller:

Font variantWeightSize
SansMedium13.7 kB
SansMediumItalic15.2 kB
SansHeavy14.0 kB
SansHeavyItalic15.5 kB
MonoMedium12.9 kB

So now the combined download "cost" for all font files is 71.3 kB (a saving of 125.6 kB).

My updated @font-face rules now look like this:

Note I've also taken this opportunity to ditch the .eot files, as they're not needed for modern browsers.

@font-face {
    font-family: "Cartograph Sans";
    src: url("/fonts/subsets/CartographSansCF-Medium-subset.woff2")
            format("woff2"),
        url("/fonts/subsets/CartographSansCF-Medium-subset.zopfli.woff")
            format("woff"),
        url("/fonts/subsets/CartographSansCF-Medium-subset.ttf")
            format("truetype");
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

Even more optimisation

Because it's been so long (years!) since I last looked at my font loading strategy, there were a couple of other things I could do to improve the situation.

  • Ditch IE! Out of gotta-support-old-Internet-Explorer habit, I've been serving .eot files for years. I've ditched IE support in my other assets a long time ago, so I can safely remove the .eot from my asset pipeline and my CSS. It also has the added aesthetic benefit of cleaning up my @font-face rules, too.
@font-face {
    /* ... */
    /* These lines can go! */
    src: url("/fonts/CartographSansCF-Medium.eot");
    src: url("/fonts/CartographSansCF-Medium.eot?#iefix")format("embedded-opentype");
    /* ... */
}
  • Preloading. Another tip from Zach's posy was to set the font links in my site's <head> to "preload"`. This makes the requests start earlier and reduces FOIT ("Flash of Invisible Text") and FOUT ("Flash of Unstyled Text"). Previously I'd been relying on the stylesheet to instigate the font download, but by preloading in the core HTML of the site I can get them downloading as soon as possible.
<link rel="preload" href="/fonts/subsets/CartographSansCF-Medium-subset.woff2" as="font" type="font/woff2" crossorigin ></link>

Conclusion

I'm really happy with the results of this work. I've saved a significant amount of bandwidth by subsetting my font files, and I've also taken the opportunity to clean up my font loading strategy. It's a shame I can't find a suitable variable font right now, but I'm happy with the results of this work. It was well worth the deep-dive.


Related posts

If you enjoyed this article, RoboTom 2000™️ (an LLM-powered bot) thinks you might be interested in these related posts:

Getting started with inline SVG icons

As a typography nerd, using a custom font to serve icons felt really good. However, it turns out inline SVG icons are better in almost every way.

Similarity score: 71% match . RoboTom says:

Inline SVG icon sprites are (still) not scary.

SVG icon sprites are a great way to maintain an icon system. This post explains how to build and use them.

Similarity score: 69% match . RoboTom says:



Signup to my newsletter

Join the dozens (dozens!) of people who get my writing delivered directly to their inbox. You'll also hear news about my miscellaneous other projects, some of which never get mentioned on this site.

    Older post:

    Stacked Sparklines web component

    Published on