Web fonts: WOFF2 is the easy 64%, subsetting is the other 30
Fonts are render-blocking and they are big. A single weight can outweigh all the images above the fold. Two changes cut a typical web font to a fraction of what a raw TTF ships — and most sites do neither.
WOFF2: the free 64%
WOFF2 is a font wrapper that compresses the glyph tables with Brotli and a transform tuned for font data. It is not a different typeface — it is the same font, smaller, and it is supported by every browser in use.
We converted 40 real fonts (Google Fonts TTFs) to WOFF2:
| TTF → WOFF2 | Reduction |
|---|---|
| median | −64% |
| range | −41% to −83% |
| 40 fonts in aggregate | 22 MB → 7 MB |
That is the single highest-leverage change you can make to a web font, and it costs nothing: same glyphs, same rendering, one build step. If you serve .ttf or even .woff, you are shipping two to three times the bytes for no reason.
Subsetting: the other 30
Here is the part almost nobody does. Open a Google Font’s TTF and it usually carries far more than Latin — Cyrillic, Greek, Vietnamese, extended punctuation, symbols. An English-language site downloads all of it and renders none of it.
Subsetting drops the glyphs a page will never use. We subset the same 40 fonts to a Latin range (0-2AF plus common web punctuation and the euro) with hb-subset:
| TTF → WOFF2, Latin subset | Reduction |
|---|---|
| median | −81% |
| 40 fonts in aggregate | 22 MB → 1 MB |
Read that last row again: 22 MB of TTF becomes about 1 MB once you both convert to WOFF2 and keep only the glyphs a Latin page uses — a 95% cut. WOFF2 alone got 22 MB to 7 MB; subsetting took 7 MB to 1 MB. The two stack, and the second is where the surprise is.
How to subset without breaking things
Subsetting is lossy in the only way that matters: the glyphs you drop are gone. Do it deliberately.
- Only drop what you don’t serve. A Latin site subsets to Latin. A multilingual site keeps the ranges it renders.
- Use
unicode-rangein@font-face. Ship several subsets (Latin, Latin-extended, Cyrillic…) and let the browser fetch only the ones a page’s text actually touches. This is exactly how Google Fonts serves fonts, and you can do it with your own. - Mind user-generated text. If users can type anything — names, emoji, other scripts — a hard Latin-only subset will show tofu (□) for missing glyphs. Keep a broader subset, or a system-font fallback, for those surfaces.
- Hinting is usually droppable. On modern high-DPI displays, TrueType hinting buys little and costs bytes; dropping it shrinks the file further, though test if you support low-DPI Windows.
What Patu does with a font
Send Patu a TTF, OTF or WOFF and it returns WOFF2. Pass the text or the unicode ranges the page needs and it subsets to them in the same step — the 95% path, not just the 64% one.
# convert + subset to the glyphs this page actually uses
curl -X POST "https://patu.dev/v1/compress?subset-text=$(cat page.txt)" \
-H "X-Api-Key: $PATU_KEY" -H "Content-Type: font/ttf" \
--data-binary @Inter.ttf -o Inter.woff2
The WOFF2 conversion is the change everyone should already have made. The subsetting is the one that turns a font from a real page-weight line item into a rounding error.
Method and data: 40 real fonts (Google Fonts TTFs); WOFF2 via woffify, subsetting via hb-subset to 0-2AF,20AC,2018-201F,2010-2014. Reductions are the median against the source TTF; the aggregate is the summed bytes of all 40.