How to how woff2 improves core web vitals and page speed
- Step 1Find the font on the critical path — Run Lighthouse / PageSpeed Insights and check whether the LCP element is text in a custom face. In DevTools, filter Network by Font and note total transferred bytes and when each font finishes relative to LCP.
- Step 2Convert every web font to WOFF2 — Run each TTF/OTF through [TTF to WOFF2](/font-tools/ttf-to-woff2) (or your build pipeline). This is the cheap, lossless win — same rendering, fewer bytes. Drop TTF/WOFF fallbacks unless analytics show real legacy traffic.
- Step 3Subset to cut glyph count — Compression shrinks bytes; subsetting removes glyphs you never render. A Latin-only subset often turns a 200 KB face into a 15–25 KB WOFF2. Use the [Font Subsetter](/font-tools/font-subsetter) or [Latin Filter](/font-tools/latin-filter).
- Step 4Set font-display deliberately — `swap` shows fallback text immediately then swaps (good for body copy, can cause a flash); `optional` avoids the swap on slow links entirely (best for CLS). Choose per use case with the [Font Display Strategy](/font-tools/font-display-strategy) tool.
- Step 5Preload the critical weight only — Add `<link rel="preload" as="font" type="font/woff2" crossorigin>` for the one weight on the LCP path. Preloading everything competes with the LCP image/CSS — preload exactly one. Generate it with the [Preload Tag Builder](/font-tools/preload-tag-builder).
- Step 6Re-measure and compare — Re-run Lighthouse on the same network throttle. Expect lower total font bytes and a shorter LCP dependency; if CLS moved, check that your fallback's metrics (size-adjust / line-height) match the web font.
Which CWV metric WOFF2 actually moves
WOFF2 reduces transfer size; the metric impact depends on what else is on the critical path. Be precise about what it does and doesn't fix.
| Metric | WOFF2's role | What else you need |
|---|---|---|
| LCP | Direct help when the LCP element is text in a custom font — fewer bytes before final paint | Preload the critical weight; keep CSS off the critical path slim |
| CLS | Indirect — smaller file swaps sooner, less time in fallback | Metrics-matched fallback (size-adjust, ascent-override) + font-display |
| TBT / INP | Minor — a smaller font costs slightly less main-thread decode time | Avoid huge inline fonts; defer non-critical JS |
| FCP | Helps only if font-display: block was delaying first paint | Use swap/optional so text paints before the font lands |
Lever comparison: compression vs subsetting vs delivery
WOFF2 is one of three independent levers. The biggest wins stack them. Sizes are illustrative for a Latin text weight — always measure your own.
| Lever | Typical effect | Tool |
|---|---|---|
| TTF → WOFF2 (compress) | ~30–65% smaller, lossless | TTF to WOFF2 |
| Subset to Latin | 200 KB → ~15–25 KB WOFF2 | Font Subsetter / Latin Filter |
| Strip hinting (web rendering) | A few KB; safe for modern AA rendering | Hinting Stripper |
| Preload critical weight | Starts the fetch earlier, parallel to CSS | Preload Tag Builder |
| font-display: optional | Removes the swap on slow links → near-zero CLS | Font Display Strategy |
Cookbook
The exact markup that turns a smaller WOFF2 into a measurable CWV win. WOFF2 alone reduces bytes; these pairings reduce the metric.
Preload + @font-face for the LCP-path weight
ExamplePreload exactly the one weight your hero/heading uses. crossorigin is required even for same-origin font preloads or the browser fetches the font twice.
<!-- in <head>, before the CSS that references it -->
<link rel="preload" href="/fonts/Brand-700.woff2"
as="font" type="font/woff2" crossorigin>
@font-face {
font-family: "Brand";
font-weight: 700;
font-display: optional; /* no swap on slow links → protects CLS */
src: url("/fonts/Brand-700.woff2") format("woff2");
}Metrics-matched fallback to kill the swap shift
ExampleA smaller WOFF2 swaps in sooner, but the shift still happens if the fallback's metrics differ. size-adjust + overrides make the fallback occupy the same space.
@font-face {
font-family: "Brand Fallback";
src: local("Arial");
size-adjust: 105%;
ascent-override: 92%;
descent-override: 24%;
}
body { font-family: "Brand", "Brand Fallback", sans-serif; }Subset then compress for the smallest LCP dependency
ExampleCompression and subsetting stack. Cut glyphs first, then WOFF2-compress what's left — the order that yields the smallest critical-path file.
Step 1: /font-tools/font-subsetter → Latin subset (drops ~90% of glyphs) Step 2: /font-tools/ttf-to-woff2 → Brotli-wrap the subset Result: a ~15–25 KB WOFF2 on the LCP path instead of a 200 KB TTF
Don't preload everything — one weight only
ExamplePreloading every weight competes with the LCP image and CSS for bandwidth. Preload the single critical weight; let the rest load with font-display: swap.
<!-- GOOD: one critical weight -->
<link rel="preload" href="/fonts/Brand-700.woff2" as="font"
type="font/woff2" crossorigin>
<!-- AVOID: preloading 5 weights — starves the LCP element -->Self-host instead of a font CDN on the LCP path
ExampleA third-party font origin adds a DNS + TLS handshake before the font even starts. Self-hosting your WOFF2 removes that round-trip from the critical path.
# Generate self-hosted @font-face, then serve the .woff2 from your domain /font-tools/google-fonts-css-generator → @font-face # host /fonts/*.woff2 on the same origin → no extra connection setup
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Switched to WOFF2 but LCP didn't improve
Render-blocking elsewhereIf the font isn't the gating resource — e.g. a large LCP image or render-blocking CSS/JS is slower — shrinking the font won't move LCP. Confirm in the Lighthouse 'LCP element' and the request waterfall which resource actually gates the paint before attributing the win to fonts.
font-display: block is delaying first paint
Misconfigured displayblock hides text until the font loads (up to ~3s), which hurts FCP/LCP. A smaller WOFF2 shortens but doesn't remove the block window. Use swap (body) or optional (CLS-sensitive). The Font Display Strategy tool maps use case to value.
Preload without crossorigin causes a double fetch
Wasted bandwidthFonts are always fetched in CORS mode, so a <link rel=preload as=font> without crossorigin doesn't match the actual font request — the browser downloads the font twice, hurting the very metric you tried to improve. Always add crossorigin.
CLS got worse after enabling swap
Metrics mismatchswap paints fallback text immediately then swaps to the web font; if the fallback's metrics differ, the swap reflows text and spikes CLS. Match metrics with size-adjust/ascent-override/descent-override, or use optional to skip the swap on slow connections.
Inlining the WOFF2 as base64 to 'save a request'
Often hurts LCPBase64-inlining a font into render-blocking CSS adds ~33% size and blocks first paint on the CSS download. For the LCP path, an external preloaded WOFF2 usually wins. See the trade-offs in the Font to Base64 tool's guidance before inlining.
WOFF2 on a third-party font CDN
Extra connection setupA cross-origin font host adds DNS + TLS before the font fetch starts — costly on the critical path. Self-host the WOFF2 on the same origin (generate the block with Google Fonts CSS Generator) to drop the extra round-trip.
CJK font stays heavy even as WOFF2
Subset, don't just compressWOFF2 saves ~25–35% on CJK because the glyph data is dense. A multi-MB CJK face is still multi-MB after wrapping. The CWV fix is subsetting to the characters in use (per page or per locale), then WOFF2-compressing the subset.
Using a system font stack — WOFF2 is irrelevant
No download to optimiseIf your design uses a system font stack (-apple-system, Segoe UI, …) there's no web-font request at all, so WOFF2 doesn't apply. Build a clean stack with the System Font Stack Generator — zero font bytes is the ultimate font-performance win.
Frequently asked questions
Does switching to WOFF2 alone fix my LCP?
Not always. WOFF2 reduces the font's transfer size, which helps when the LCP element is text in a custom font on a slow link. But a smaller file that's still render-blocking, or a page where the LCP is gated by an image/CSS instead, won't improve from the font change alone. Pair WOFF2 with preload, font-display, and subsetting.
How much smaller is WOFF2 than TTF for performance?
Typically 30–65% smaller for the same glyphs — Brotli plus the glyf/loca transform pack the SFNT tables far tighter than uncompressed TTF. The exact figure depends on the font; Latin text faces compress best, CJK and icon fonts least. Always measure on your own files.
Will WOFF2 reduce CLS?
Indirectly. A smaller WOFF2 arrives sooner, so the time spent in the fallback (and the swap) is shorter. But the layout shift itself comes from the fallback and web font having different metrics. Eliminate it with a metrics-matched fallback (size-adjust, ascent-override, descent-override) and a deliberate font-display.
What font-display value is best for Core Web Vitals?
For CLS-sensitive pages, optional is strongest — it skips the swap entirely on slow connections, so there's no reflow. For body copy where you want the brand font to win, swap paints fallback text immediately then swaps. Avoid block on the critical path; it delays first paint. Use the Font Display Strategy tool to choose.
Should I preload my WOFF2 fonts?
Preload the single weight on the LCP path — and only that one. Preloading every weight competes with the LCP image/CSS for bandwidth and can make things worse. Use <link rel="preload" as="font" type="font/woff2" crossorigin>; the crossorigin attribute is mandatory or the font is fetched twice.
Is subsetting or compression more important for speed?
They're different levers and the biggest wins stack them. Subsetting removes glyphs you never render (often the larger absolute saving); WOFF2 compresses what remains. For the smallest critical-path file, subset first (Font Subsetter), then convert to WOFF2.
Does WOFF2 help users on metered mobile data?
Yes, materially. A 5-weight family at 1 MB of TTF can become ~300 KB of WOFF2 — a 700 KB saving per first-time visitor. Multiplied across your DAU, the bandwidth and cost savings are significant, even if the per-visitor LCP delta is modest on fast networks.
Should I lazy-load fonts to improve LCP?
Generally no — lazy-loading delays the font request, which makes text-LCP slower, not faster. The better pattern is: subset, ship as WOFF2, and preload the one critical weight so it starts fetching in parallel with the CSS.
Does WOFF2 work when I use a system font stack?
There's nothing to optimise — a system font stack downloads no font at all, so WOFF2 is irrelevant. If performance is the goal and brand fidelity allows it, a system stack (built with the System Font Stack Generator) is the fastest possible option.
Can I shave more bytes by stripping hinting?
On the web, often yes — modern anti-aliased rendering largely ignores TrueType hinting, so the Hinting Stripper can remove a few KB safely. Strip hinting, then WOFF2-compress. Keep hinting only if you target small sizes on older Windows GDI rendering.
Is the conversion safe to run on production fonts?
Yes. WOFF2 is a lossless wrapper — the SFNT tables, outlines, metrics, kerning, and OpenType features are byte-equivalent after the browser decompresses. There's no rendering risk; the only effect is fewer bytes on the wire.
Will Google's page-experience signals reward this?
Core Web Vitals (including LCP and CLS) are page-experience ranking signals, and font delivery is a common lever for both. WOFF2 + subsetting + preload + sensible font-display is a well-trodden path to a green CWV assessment for font-heavy designs — just measure to confirm the font was actually the bottleneck.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.