Fonts

Fonts are the heaviest thing on most text pages. This guide compares the usual ways to load them and shows the cheapest one, using this site's own files as the example.

The options and what they cost

A system font stack costs nothing: no file, no request. The price is that the page looks different on every platform.

Self-hosting one variable file costs a single request to your own host, involves no third party, and leaves a strict content security policy strict. You do the subsetting and the caching yourself.

A font service on another host is the easiest to add. It costs an extra connection, and browsers now keep a separate cache for each site, so a font another site loaded from the same service is not reused. The policy also has to allow the third-party host.

A package such as Fontsource gives you pinned versions and self-hosting, and needs a build step. Shipping one file per weight and style works, but it costs several requests where one variable file would do.

What this site ships

Two files: plex-sans-var.woff2, 40 KB, holds every weight of the text face, and plex-mono-400.woff2, 10 KB, one weight for code. Both are served from this site with Cache-Control: public, max-age=31536000, immutable, so a returning reader downloads them once for a year.

Only the sans file is preloaded, because every page draws its text with it. A preload says the file is needed at once, and the mono file is not needed by every page.

The font cache rule in public/_headers
/fonts/*
  Cache-Control: public, max-age=31536000, immutable

Cut the file to what you use

unicode-range tells the browser which characters a file covers, so it fetches the file only when the page needs one of them. This site's block covers Latin, Latin-1 punctuation and the arrows and signs it uses, and nothing else. A language that needs more gets its own file and its own range, loaded only on its own pages.

Subsetting the file itself matters as much: a variable font cut to the range and to the axes in use is a fraction of the full family.

The text face of this site
@font-face {
  font-family: 'IBM Plex Sans';
  font-weight: 200 700;
  font-display: swap;
  src: url('/fonts/plex-sans-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+2000-206F, U+20AC;
}

font-display: show something at once

font-display: swap draws text in the fallback face right away and switches when the file arrives. optional gives the file a very short window and, if it misses it, keeps the fallback for that page view. Choose swap for text a reader has come to read, and optional where the font is decoration.

A fallback tuned to the real font

When the real font replaces the fallback, the layout shifts if the two have different sizes and line boxes. size-adjust scales the fallback's glyphs, and ascent-override, descent-override and line-gap-override set its line box, so a system font can be made to occupy the same space as the one that is coming. This site declares IBM Plex Sans Fallback and IBM Plex Mono Fallback this way and names them after the real families in its font stacks.

How much shift is left depends on the fallback the reader's system has, so measure it in your own page with the layout shift figure in the browser's developer tools. This guide has not measured it.

A fallback with Arial scaled to Plex
@font-face {
  font-family: 'IBM Plex Sans Fallback';
  src: local('Arial');
  size-adjust: 100.8%;
  ascent-override: 101.7%;
  descent-override: 27.3%;
  line-gap-override: 0%;
}