How to plan font glyph budgets for performance targets
- Step 1Derive the per-font byte budget — Start from the page-weight budget and font allocation (fonts are commonly 5–10% of a page budget). A 1 MB page budget → ~50–100 KB of fonts; split across body + heading + mono → roughly 20–35 KB each. Write the per-font number down before you measure anything — measuring first invites moving the goalposts.
- Step 2Analyse each candidate font — Drop each candidate WOFF2 (or TTF/OTF/WOFF) onto the analyser. For the truest baseline, build and upload the WOFF2 — that makes `current_woff2_estimate_bytes` the real file size instead of a `sfnt × 0.55` estimate.
- Step 3Find the smallest subset that covers your languages — In the projections, identify the smallest subset whose `glyph_count` actually covers the scripts you ship. English-only → Latin Basic. Add CEE languages → Latin Extended. The analyser projects all six in one pass, so you don't re-upload per subset.
- Step 4Compare that subset's projection to the budget — Read `estimated_woff2_bytes` for the chosen subset. Under budget → the font is viable. Over budget at every subset that covers your languages → the font is too heavy for this page, regardless of how nice it looks.
- Step 5Build the winner and confirm against budget — Subset to the chosen charset with [the font subsetter](/font-tools/font-subsetter) and measure the real WOFF2. Because the projection is conservative, the shipped file is usually at or below the projection. If it's over, strip hinting and re-measure.
- Step 6Pin the budget and watch it — Record the per-font budget (e.g. "Body: Inter Latin, ≤22 KB") in the design-system doc. Re-run the analyser (or the runner API) when a font version changes — foundry updates can quietly add glyphs and push you over.
Page budget → font allocation → per-font budget
A worked split for common page-weight budgets. Font allocation is a planning convention (5–10% of page weight), not a tool output. The per-font column is what you compare the analyser's projection against.
| Page weight budget | Font allocation (5–10%) | Fonts on page | Per-font budget (approx) |
|---|---|---|---|
| 500 KB (lean / 3G-aware) | 25–50 KB | Body only | ≤30 KB body, system stack for the rest |
| 1 MB (typical) | 50–100 KB | Body + heading | ≤35 KB body, ≤35 KB heading |
| 1 MB (typical) | 50–100 KB | Body + heading + mono | ≤25 KB each |
| 2 MB (rich brand) | 100–200 KB | Body + heading + 2 display weights | ≤40 KB each |
Reading a projection against a 30 KB budget
Illustrative projections from a 1,300-glyph webfont with current_woff2_estimate_bytes ≈ 80 KB. Verdict is the per-subset estimate vs a 30 KB per-font budget. Numbers follow the analyser's own formula; your font will differ.
| Subset | Projected WOFF2 | savings_pct | Fits 30 KB budget? |
|---|---|---|---|
Latin Basic + Latin-1 Supplement | ~15 KB | 81% | Yes — comfortable headroom |
Latin Extended-A + Extended-B | ~29 KB | 64% | Yes — tight, watch foundry updates |
Cyrillic | ~18 KB | 78% | Yes |
Greek | ~14 KB | 83% | Yes |
| Full font (no subset) | ~80 KB | 0% | No — 2.7× over budget |
Cookbook
Budget scenarios from real performance reviews, with the projection that settled them. All KB figures are projections from the analyser's formula — confirm with a real subset before you ship. For the language-fitness half of a budget decision, pair this with the character coverage map.
Heading font fails at every viable subset
ExampleA display face the design team loved projected over a 35 KB heading budget even at Latin Basic. That's the analyser doing its job — it killed a bad choice before it reached the critical path and hurt LCP.
Candidate: Display-Bold.woff2 current_woff2_estimate_bytes: 96000 budget (heading): 35000 Latin Basic projection: ~21 KB -> fits? would, BUT the brand needs Latin Extended (Polish + Czech market): Latin Extended projection: ~52 KB -> over budget by 17 KB Verdict: too heavy for this market. Pick a lighter display face or drop a weight from the budget.
Splitting one budget across three fonts
ExampleA 1 MB page budget allocating 75 KB to fonts, spread over body, heading, and mono. Each candidate is analysed; the projections have to sum under 75 KB.
Allocation: 75 KB total for fonts. Body (Inter, Latin Basic): ~22 KB Head (Fraunces, Latin Basic): ~28 KB Mono (JetBrains, Latin Basic): ~20 KB ----------------------------------------- Sum: ~70 KB -> under 75 KB budget. Ship. If the sum had exceeded 75 KB: drop mono to a system stack with the system font stack generator, reclaiming ~20 KB.
Conservative projection buys budget headroom
ExampleThe analyser projected 22 KB for the body font's Latin subset; the real subset came out at 19 KB. The 1.25× overhead factor makes the projection lean high, so budgets set against it rarely blow when the real file ships.
Projection (analyser): estimated_woff2_bytes = 22000 Real subset (font-subsetter output): 19100 Delta: -13% (real file UNDER projection) Lesson: set the budget at the projection. If the real file lands under it, you have headroom for a foundry update later.
Automated budget check in CI
ExampleRun the analyser through the runner API on each build and fail if the full-font baseline grew beyond what your subset budget can absorb. Empty schema means you just POST the file.
#!/usr/bin/env bash
set -e
JSON=$(curl -sS -X POST \
http://127.0.0.1:9789/v1/tools/glyph-count-analyzer/run \
-F 'files=@public/fonts/Body.woff2')
LATIN=$(echo "$JSON" | jq '.projections[]
| select(.subset|startswith("Latin Basic"))
| .estimated_woff2_bytes')
if [ "$LATIN" -gt 24000 ]; then
echo "Body Latin subset projects ${LATIN}B > 24KB budget"; exit 1
fiVariable vs static, by budget
ExampleA family needs four weights. The analyser shows the variable file's glyph total is the same as one static, but the variable WOFF2 replaces four files. Compare the variable size to the sum of four static subsets to decide.
Four static Latin subsets: 4 x ~20 KB = ~80 KB One variable Latin subset: ~38 KB (one file, all weights) Variable wins the budget at 3+ weights. Freeze unused axes first with the variable font freezer, subset, then re-analyse to confirm the single-file size fits the per-family budget.
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.
Projection is conservative, so budgets set against it hold
By design (leans high)The proportion × 1.25 factor inflates the projection to account for per-glyph table overhead, and the real subset usually comes out a bit under. That's intentional for budgeting: a budget set at the projected number gives you a small safety margin. The risk is the other direction — for complex scripts the projection can under-shoot — so verify the real subset before locking a tight budget.
TTF baseline inflates the whole budget table
ExpectedIf you analyse the source TTF instead of the built WOFF2, the baseline is sfnt × 0.55, a guess. A font with heavy hinting compresses worse than 0.55× and your projections under-state the real size — dangerous for a tight budget. Always feed the analyser the actual WOFF2 you intend to ship so the baseline, and every projection scaled off it, reflects real bytes.
Budget set on full font, not subset
Avoidable mistakeA common error is comparing the budget to current_woff2_estimate_bytes (the full font) and concluding the font is too heavy. The budget should be compared to the projected size of the subset you'll actually ship. A 90 KB full font that projects 18 KB at Latin Basic fits a 30 KB budget fine — you just have to subset it. Read the projection row, not the headline.
Glyph total counts variants the subset will drop
By designtotal_glyphs includes small-caps, oldstyle figures, and other OpenType-feature glyphs you may not ship. If your subset strips those features, the real file is lighter than a naive read of the glyph count suggests — but the projection already works off codepoint coverage, not the raw total, so the projected size is closer to reality than the total glyph count would imply. Don't budget off total_glyphs directly; budget off the per-subset estimated_woff2_bytes.
Two scripts on one page double the font cost
ExpectedIf a page renders both Latin and Cyrillic, you can't pick one subset — you ship both, and the budget must cover the sum (or a single combined subset). The analyser projects each independently; add the rows for the scripts you actually render. Better: ship separate unicode-range @font-face blocks so each visitor downloads only the script on their page, and budget per-script.
Icon font defeats the budget logic
Expected (low coverage)Icon fonts map into the Private Use Area, which none of the six subsets cover, so every projection shows ~100% savings and a near-floor size. That would suggest a near-free budget, but subsetting to "latin" would delete every icon. For icon fonts, ignore the subset projections and budget against the whitelisted-glyph build from the character whitelist builder.
File exceeds the tier limit before it can be budgeted
413-style rejectA font above the active tier's size limit (free 5 MB / pro 50 MB / developer 1 GB) is refused with "exceeds the {tier} tier per-job limit" before parsing — you can't even get a projection. Large CJK fonts hit this routinely on the free tier. For those, see the CJK considerations guide, which covers the size realities up front.
Preload changes the budget calculus, not the bytes
Out of scope hereBudget is about bytes; LCP is about bytes plus when they're fetched. The analyser only gives you the bytes. A font that fits the budget can still hurt LCP if it's discovered late — <link rel=preload> and font-display: swap fix the timing. Plan the byte budget here, then plan the timing with the preload tag builder and the font-display strategy tool.
Frequently asked questions
What's a sensible font-weight budget per page?
Common splits: lean/3G-aware sites keep total fonts under ~30 KB (often a system stack plus one webfont); typical sites land 50–100 KB; rich-brand sites run 150–250 KB. The analyser doesn't set the budget — you do — but it tells you whether a given font, subset to your languages, fits the number you chose. Compare its per-subset estimated_woff2_bytes to your per-font budget.
Should I budget against the full font or the subset?
The subset. current_woff2_estimate_bytes is the full font and almost always blows a tight budget — that's expected. The number that matters is the projected size of the smallest subset that covers your audience's languages. A 90 KB font that projects 18 KB at Latin Basic comfortably fits a 30 KB budget once subset. Read the projection row, not the headline.
How reliable is the projection for budgeting?
Reliable enough to plan with, and slightly conservative by design. The × 1.25 overhead factor makes projections lean high, so a subset that fits in the table usually fits for real. The exception is complex scripts where the model can under-shoot — verify those with a real subset before locking a tight budget. For typical Latin work, the projection is within ±5%.
Does font weight actually affect LCP?
It can, when the font is on the critical render path and discovered late. Fewer font bytes means the fetch finishes sooner, and with font-display: swap the fallback paints immediately. But timing matters as much as size — a small font fetched late still delays text. Use this tool to win the byte budget, then fix discovery with preload and a sensible font-display strategy.
Should I count Google Fonts CDN bytes in my budget?
Yes — bytes are bytes regardless of host. Self-hosting saves a DNS lookup and a TLS handshake and lets you preload, but it doesn't shrink the font. Whether you serve from Google's CDN or your own origin, apply the same per-font byte budget. To self-host with the right unicode-range splits, generate the CSS with the Google Fonts CSS generator.
How do variable fonts fit into a byte budget?
A variable font replaces multiple static weights with one file. Its glyph total equals a single static instance, but the file size is larger than one static and smaller than the sum of many. Compare the variable WOFF2 size to the sum of the static subsets you'd otherwise ship — variable usually wins at 3+ weights. Freeze unneeded axes with the variable font freezer first, then analyse.
What if even Latin Basic blows my budget?
Then the font is too heavy for that budget and you have three moves: pick a lighter typeface, raise the budget (with the LCP cost that implies), or fall back to a system stack for that role and spend the budget elsewhere. The analyser makes this call fast — if the smallest covering subset is over budget, you've eliminated the font in one upload.
Can I track budgets automatically so regressions get caught?
Yes. Pair the @jadapps/runner and POST each font to http://127.0.0.1:9789/v1/tools/glyph-count-analyzer/run on every build; parse the projection and fail CI if it exceeds the budget. Because a foundry update can silently add glyphs and grow the baseline, this catches drift before it ships. The design-system budget script guide has the full implementation.
Why analyse the WOFF2 rather than the TTF for budgeting?
Because the WOFF2 input makes the baseline a real measured file size; the TTF input makes it a sfnt × 0.55 estimate that can be wrong for heavily-hinted fonts. Every per-subset projection scales off the baseline, so an accurate baseline means accurate budget numbers. Build the WOFF2 you intend to ship and analyse that file.
How do I budget a page that renders two scripts?
Add the projections for both scripts — you ship both subsets if both render. Better, ship a separate unicode-range @font-face per script so each visitor downloads only the script on their page, and budget per-script. The analyser projects each subset independently; you decide which combinations are co-resident on a page.
Does the tool measure CLS or only size?
Only size — it counts glyphs and projects WOFF2 bytes. Cumulative Layout Shift from a late-swapping font is a timing-and-metrics concern, addressed by font-display, size-adjust, and metric overrides, not by glyph count. Use the font-display strategy tool and the font metrics analyzer for the layout-shift side of the budget.
Is my font sent to a server when I check a budget?
No. Parsing and projection run entirely in your browser via WebAssembly; the font bytes never leave the page. Only an anonymous processed-file counter is recorded for dashboard stats, with no content, and it's opt-out. You can budget against confidential or pre-release foundry fonts without exposing them.
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.