Font Optimization Guide

Complete guide to reducing web font payload and improving Core Web Vitals. WOFF2 conversion, subsetting, variable fonts, preloading, and the specific metrics you can expect from each step.

Font optimization checklist

Convert to WOFF2 — every font in your project should be WOFF2. Expect 50–70% reduction from TTF/OTF, 25–40% from WOFF.
Subset your fonts — strip unused scripts. A font limited to Basic Latin is often 60% smaller than one with Cyrillic and Greek.
Use variable fonts when available — one WOFF2 variable file replaces 4–9 static files.
Preload only the critical font — the font used in the first rendered view, usually one file.
Use font-display: swap — show fallback text immediately.
Self-host fonts — removes two DNS lookups and enables stable preload URLs.
Set long cache headers — Cache-Control: public, max-age=31536000, immutable.

Step 1 — Convert to WOFF2

WOFF2 uses Brotli compression and produces the smallest usable font files. Every modern browser (Chrome 36+, Firefox 39+, Safari 12+, Edge 14+) supports it.

Source formatTypical WOFF2 sizeReduction
TTF25–35% of original65–75%
OTF25–35% of original65–75%
WOFF60–75% of original25–40%

Convert with the TTF to WOFF2 Converter, OTF to WOFF2 Converter, or WOFF to WOFF2 Converter.

Step 2 — Subset to the glyphs you actually use

Most fonts ship with support for multiple scripts — Latin, Cyrillic, Greek, Devanagari, Arabic, CJK. If your site only targets English readers, you are downloading 40–70% of unused glyphs.

Two approaches:

  1. Manual subsetting with fonttools — run pyftsubset on your font file with the exact characters or Unicode ranges you need:
pyftsubset Inter.ttf \
  --unicodes="U+0020-007E" \
  --flavor=woff2 \
  --output-file=Inter-Latin.woff2
  1. Unicode-range subsetting in CSS — serve multiple subsets with unicode-range declarations. The browser downloads only the subset that matches the characters actually used on the page:
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

Step 3 — Prefer variable fonts

A static font family typically ships as separate files for each weight: 100, 300, 400, 500, 700, 900. That is six files, six network requests, and roughly 900KB total for a font like Inter.

A variable font replaces all of these with one WOFF2 file of around 100–200KB. The CSS declares a weight range:

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-var.woff2") format("woff2");
  font-weight: 100 900;
}

The tradeoff: variable font files are larger than a single static weight, but smaller than the total of multiple weights. If you only use one weight, a single static file may be smaller. Use a variable font when you need 2+ weights.

Step 4 — Preload and font-display

See the Font Loading Strategy Guide for the complete walkthrough of preload, font-display, and metric-adjusted fallbacks.

Step 5 — Cache aggressively

Fonts are static assets. Once a version is deployed, it never changes. Serve them with:

Cache-Control: public, max-age=31536000, immutable

When you update the font, use a new filename (or a query string like ?v=2). The new file gets a fresh cache entry; the old file is cached forever, which is fine.

On Cloudflare Pages, this is configured in _headers:

/assets/fonts/*
  Cache-Control: public, max-age=31536000, immutable

Measuring the impact

After applying these optimizations, measure the improvement using real-user data in Google Search Console's Core Web Vitals report — not just Lighthouse lab scores. The metric to track is LCP (Largest Contentful Paint), which is typically the headline text using your font.

Typical improvement from optimizing a poorly-loaded web font:

OptimizationTypical LCP improvementTypical byte savings
TTF to WOFF2 conversion200–500ms60–75%
Subsetting100–300ms40–70%
Variable font (vs multiple static)300–800ms60–80%
Preload with crossorigin100–400ms0
font-display: swapEliminates invisible-text LCP delay0

Frequently asked questions

What is the fastest web font format?

WOFF2 uses Brotli compression and produces the smallest usable files. It is supported by every modern browser and reduces file size by 30% versus WOFF and 65% versus TTF/OTF.

What is font subsetting?

Removing unused glyphs from a font file. If your site only needs Basic Latin, you can strip Cyrillic, Greek, and CJK glyphs, often reducing file size by 40–70%. Use fonttools pyftsubset or a subsetting service.

Are variable fonts smaller than static fonts?

One variable font file is smaller than 4+ static files combined. A variable font covering 100–900 weight in WOFF2 is typically 100–200KB. The equivalent 9 static WOFF2 files total 700–1500KB.

Is font subsetting legal?

It depends on the license. SIL OFL 1.1 explicitly permits subsetting as long as you do not use the Reserved Font Name (if one is declared). Apache 2.0 also permits subsetting. Commercial licenses often restrict modification — check before subsetting.

What is a good font file size for web?

Under 100KB per WOFF2 file is good. Under 50KB is excellent. Anything over 200KB should be subsetted further or replaced with a lighter alternative.

Related tools