Web Font Loading & The Status Quo
Tags: javascript, html, font, webfonts
Published: 2021-02-18 01:31:06

---
title: Web Font Loading & The Status Quo
published: true
date: 2021-02-16 00:00:00 UTC
tags: javascript, html, font, webfonts
canonical_url: https://whistlr.info/2021/font-loading/
---

Let's start with the obvious: there's lots of great posts out there on font loading (which all tend to be 27 pages long for some reason) and using the `font-display` CSS property, and… you get the idea. These all _accept_ the status quo—that fonts cannot load synchronously like your CSS—and just describe ways to mask that.

But, it's my website, and I know exactly what fonts the user is going to need. So why can't I ask the browser to put a small font onto the critical path before a page displays at all? As an engineer, I find the lack of choice frustrating. 😠

I don't have a perfect soution, but this post lays out my gripes, a fallback solution via base64 encoding your fonts, and platform suggestion. To start, here's the fundamental issue, shown via animation.

<img src="https://storage.googleapis.com/hwhistlr.appspot.com/assets/emojityper-2020-08-10.webp" width="768" height="524" />

While there's variants on this problem, there's two things happening here:

1. "Emojityper" displays with the system font first
2. The loaded font is _bigger_ than the system font—we see [layout shift](https://web.dev/cls/), which I'm paid by my employer to tell you is bad (it _is_ bad, but I'm also paid to tell you)

The status quo solution is to use the `font-display` CSS property (and some friends). And to be fair, traditional CSS can solve both of these problems. However, these issues are typically solved by _not displaying the offending text_ until its font arrives&mdash;even though the rest of your page is rendered.

The most frustrating issue here is that this "flash" takes all of a few frames—maybe 50-60ms. This is the choice I'd like: to delay rendering by a small amount of time. My opinion on this UX is that users are going to be more delighted by a page ready-to-go rather than one effected by a flash that confuses a user's eyes for mere milliseconds. 👀

### Case Study

On [developer.chrome.com](https://developer.chrome.com), we actually inline all of our stylesheets and images (largely SVGs) into each page's HTML in order to reduce the number of requests and make the page load faster. We're really happy with this solution, because for most users, their network is going to deliver that whole _single_ payload incredibly quickly.

Despite this sheer duplication of assets across every HTML page, our fonts still go to the network, and new users will still see a flash.

## Loading in general

For background on loading, see my [recent interactive post](https://whistlr.info/2020/understanding-load/). The TL;DR from that post is that the _only_ thing that can block a page from rendering is loading external CSS. And for fonts, your browser will asynchronously load a font when glyphs from it are needed—e.g., for the heading font of this blog, that's immediately, but only once the stylesheet has first arrived.

Here, I'm actually using two tricks to get you the font earlier (although neither prevents the flash and layout shift):

- I use `<link rel="preload" ... />` to request the font early, although this only helps if you have an external CSS file (if it's inlined in `<style>`, the font URL is _right there_)
- I also send the font via [HTTP2 Server Push](https://en.wikipedia.org/wiki/HTTP/2_Server_Push) _before_ any HTML goes to the user, although it seems like browser vendors are removing support for this due to misuse

Regardless of what you think this post, preloading your font is a good idea. Modern HTTP is very good at sending you lots of files at once, so the earlier your user's font can get on that train, the better. 🚂🚋🚋

Font files should also be [fingerprinted](https://web.dev/love-your-cache/#fingerprinted-urls) and cached forever for future loads. I digress, but this loading issue—like so many—is only about the user's 1<sup>st</sup> load. With the advent of service workers, we as web developers have almost complete control over the user's 2<sup>nd</sup> load.

## Solutions, today

This is a tricky one. We can actually include a font inline in your blocking CSS file—by base64 encoding it, which has ~33% space overhead. There's no extra network requests here and decoding is done in a blocking way.

```
@font-face {
  font-family: 'Carter One';
  src: url('data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAAG74ABI...') format('woff2');
  font-weight: normal;
  font-style: normal;
}

```

Many folks argue that [base64 is a bad idea](https://csswizardry.com/2017/02/base64-encoding-and-performance-part-2/). Although, in that case study, the size of the image isn't listed—about 220k—and the author fundamentally disagrees with my assertion that fonts _can_ be critical resources.

There is cost here, both in space and decoding time. If you're going to base64 a font to avoid the flash, how can you minimize the cost?

- I find that most Latin custom fonts are about ~20k, and I wouldn't base64 anything substantially larger than that—keep it to a single font at most. (I'd use the [system font](https://whistlr.info/2020/system-font/) for body text, and leave a custom font for your headings or hero text.)

- Put the font declaration in a unique CSS file that's cached forever. Unlike the rest of your CSS, which you might change, the font is not going to change over time.

```
<!-- These will be downloaded in parallel -->
<link rel="stylesheet" href="./base64-encoded-font-eeb16h.css" />
<link rel="stylesheet" href="./styles-cakl1f.css" />

```

- Only ship woff2—[95%+ of users](https://caniuse.com/woff2) have support

- This is advanced, but if you can control what your user gets on their 2<sup>nd</sup> load (e.g., via a Service Worker), then you _could_ serve the user a real, cached woff2 as well and then use only it for repeat loads.

## Anti-patterns

There are other ways to ensure users don't see any part of your page before the fonts load. But they're going to involve JavaScript and that's just a rabbit hole that increases your site's complexity _real fast_. 📈

You could mark every part of your page as hidden via a CSS class, and then only remove it once you see a font arrive. You could do this via the [Font Loading API](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API) or by literally measuring the rendering size of a test `<div>` until it changes. These are not good solutions.

(This is something I happily do on [Santa Tracker](https://santatracker.google.com), but we literally have a loading screen, _lean in_ to a slow load, and the entire site requires JS. It's not suitable for _sites_.)

## A standards plea

Last year, a proposal was made to add [Priority Hints](https://wicg.github.io/priority-hints/).
Right now, this proposal is _just_ for hints about the importance of network traffic.

But perhaps it could include a hint choice of `critical` which informs a browser that this preload _may_ block page rendering&mdash;if it arrives quickly, of course.

```
<!-- Preload this font and block until used, with limited budget -->
<link rel="preload"
    importance="critical"
    href="/carter-one.woff2?v11"
    as="font"
    type="font/woff2"
    crossorigin />

<!-- This could work for as="style", as="fetch" or others -->
<link rel="preload"
    importance="critical"
    href="/important-data.json"
    as="fetch"
    crossorigin />

```

This would allow for standards-based developer _choice_, and because it's a purely additive attribute, would have a sensible fallback for unsupported browsers (i.e., not to block the page at all). There's also a wide range of resources [you can preload](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attributes), so it could be a versatile tool. ⚒️

## Summary

I find a lack of control over font loading frustrating, and using base64 for small fonts can help you if this problem frustrates you too. And if you find yourself trying to preload similarly-sized images 🖼️ to make your page work, that's actually one of the biggest signs this approach might help you—to me, that font is just as important as that site logo or navigation button. 🍔

To be clear, this can be a [footgun](https://en.wiktionary.org/wiki/footgun)—don't block page loading for minutes because 100k of fonts haven't arrived—use base64 sparingly to avoid a flash or [layout shift](https://web.dev/cls/). I don't think it makes sense for every site. I'm not even sure I'm going to implement this strategy on this blog.

Yet, to revisit the [developer.chrome.com](https://developer.chrome.com) case study from earlier, where we happily inline images and our stylesheets. I don't think we should inline the fonts directly on the page—they're ~20k files which _never change_—but moving them to a synchronous, fingerprinted (and cached forever), stylesheet including just the base64 font may be on the cards.

➡️ Let me know what you think on [Twitter](https://twitter.com/intent/tweet?text=Hi%20@samthor%20I%20think%20base64%20is%20the%20worst%20and%20fonts%20should%20not%20be%20critical%20to%20a%20page%20load%20%F0%9F%94%A5).