Picovert

Web Font Optimization: Eliminate CLS and Cut Load Time by 60%

2026-04-207 min read

Web fonts are responsible for two categories of performance problems: layout shifts (CLS) from font swapping, and load time overhead from large font files or external connections. This guide covers both, with the specific settings that achieve a CLS of 0 from fonts and cut font load time by 60%.

WOFF2: the only format you need

WOFF2 uses Brotli compression and has been supported by all major browsers since 2016. There is no reason to serve WOFF, TTF, or EOT anymore. Serving multiple formats adds complexity and bandwidth negotiation overhead. Serve WOFF2 only.

Compression comparison for a typical Latin font:

FormatFile size
TTF220 KB
WOFF140 KB
WOFF295 KB
WOFF2 subsetted (Latin only)18 KB

Subsetting: the biggest win

Most fonts include glyphs for hundreds of Unicode ranges. If your site is English-only, you only need the Latin character set — roughly 250 glyphs. Subsetting to Latin reduces a typical font from 80–150 KB to 15–25 KB.

Tools for subsetting:

  • pyftsubset (fonttools): command-line, precise control. pyftsubset font.ttf --unicodes='U+0020-007F' --flavor=woff2
  • Google Fonts with subset parameter: &subset=latin on the URL.
  • next/font: Next.js automatically subsets and self-hosts Google Fonts at build time. Zero external connection.

Self-hosting vs Google Fonts

Google Fonts requires:

  1. DNS resolution for fonts.googleapis.com
  2. TCP + TLS to fonts.googleapis.com
  3. CSS fetch to get the @font-face rules
  4. DNS resolution for fonts.gstatic.com
  5. TCP + TLS to fonts.gstatic.com
  6. Font file fetch

Self-hosting (or using next/font) removes steps 1–5. On a fast connection the difference is ~100 ms; on a slow connection it can be 500+ ms.

font-display: controlling the swap

The font-display descriptor controls what happens while the font is loading:

ValueBehaviorCLS risk
blockInvisible text for up to 3 sLow (no layout shift, but FOIT)
swapFallback immediately, swaps when readyHigh (layout shift on swap)
fallbackVery brief block, then swapMedium
optionalVery brief block, no swap if not readyZero (never swaps)

For body text: font-display: optional eliminates CLS entirely but means some users see the fallback font. For headings: font-display: swap is acceptable since a heading shift is less visually jarring than body text shift.

Preloading fonts

Add a preload link for your most critical font file in <head>:

<link rel="preload" as="font" type="font/woff2" href="/fonts/inter.woff2" crossorigin="anonymous">

The crossorigin attribute is required even for same-origin fonts, because browsers fetch fonts in anonymous CORS mode. Missing it causes a double download.

Variable fonts

A variable font contains all weights and styles in a single file. A traditional font family (regular + bold + italic + bold-italic) ships 4 files. A variable font ships 1 file — usually smaller than any single weight:

ApproachFilesTotal size
Static fonts (4 weights)480 KB each = 320 KB
Variable font1~70 KB

Use variable fonts when you need more than 2 weights of the same family. The font must support variable format — most modern Google Fonts do.