How to use font preload to improve core web vitals
- Step 1Run Lighthouse and identify the LCP element — Open DevTools → Lighthouse (or Performance → LCP marker). Find the largest contentful paint element — usually a hero headline or first paragraph. Note which `font-family` it renders in. *That* font is your one preload target; nothing else.
- Step 2Get the exact WOFF2 URL the LCP font uses — DevTools → Network → filter Font → reload. Find the request for the LCP font and copy its URL verbatim, including any hash in the filename. Preloading a URL that doesn't match this request does nothing useful.
- Step 3Generate the preload tag — Paste that single URL into [the builder](/font-tools/preload-tag-builder) and Generate. You'll get one `<link rel="preload" as="font" type="font/woff2" href="..." crossorigin>`. Resist pasting the body, footer, and icon fonts — one line in.
- Step 4Place it high in <head>, before the stylesheet — Put the tag above the render-blocking `<link rel="stylesheet">` so the preload scanner discovers the font before the CSS. For a cross-origin font, add a `preconnect` to its host just above the preload.
- Step 5Set font-display so the fallback shows immediately — Preload reduces the time to the font, but `font-display` governs what shows meanwhile. `swap` renders fallback text immediately (good for headlines); `optional`/`fallback` suit body text. Pick a value with [font-display-strategy](/font-tools/font-display-strategy) so preload + display work together rather than fighting.
- Step 6Re-measure and confirm the font is actually used — Re-run Lighthouse on a throttled connection. LCP should drop 100–500 ms. Check the console for `preloaded but not used` — if present, the URL didn't match the LCP font fetch (path/casing/cache-buster/crossorigin mismatch). If LCP didn't move, you preloaded the wrong font.
Per-vital impact of font preload
Preload primarily targets LCP. Its CLS effect is indirect (earlier swap) and is best paired with metric overrides. INP is essentially unaffected.
| Vital | Effect of preloading the LCP font | Caveat |
|---|---|---|
| LCP | 100–500 ms faster on cold-cache slow connections | Only if the preloaded URL IS the LCP-element font |
| CLS | Indirect improvement — font swaps in earlier, layout settles sooner | The swap can still shift; neutralise with size-adjust/ascent-override |
| INP | No meaningful effect | Preload is a network hint, not an interactivity one |
| FCP | Neutral to slightly positive with font-display: swap | Over-preloading delays FCP by stealing early bytes |
How many fonts to preload
The tool will emit a tag for every URL you paste — no cap, no warning. The right number is almost always 1, occasionally 2.
| Count | When | LCP effect |
|---|---|---|
| 1 | Single LCP-element font (the default, correct case) | Best — full benefit, minimal contention |
| 2 | Distinct heading + body fonts both above the fold on the LCP path | Usually still positive; watch the waterfall |
| 3+ | Almost never justified | Fonts fight for first bytes — often regresses LCP |
| 0 | Font isn't on the LCP path, or LCP element is an image | Don't preload a font — preload the LCP image instead |
Preload won't help when…
Common situations where a font preload is wasted or counter-productive — verify before adding one.
| Situation | Why preload doesn't help | Do instead |
|---|---|---|
| LCP element is an image | The font isn't the LCP resource | Preload the hero image (as="image"), not the font |
| Returning visitor (warm cache) | Font already in disk cache | Preload still helps first visits; accept the small warm-cache delta |
font-display: block with a slow font | Text stays invisible during load regardless | Switch to swap/fallback so text shows while loading |
| Preloaded URL ≠ @font-face URL | Browser downloads the font twice | Copy the exact Network-tab URL; keep crossorigin |
Cookbook
Field-tested patterns for using preload to move LCP, not just to add tags. Generate the preload lines with the builder; the surrounding context is hand-written.
One headline font, self-hosted
ExampleThe textbook win: the LCP element is an H1 in a brand font, self-hosted as WOFF2. Preload that one file, set swap so the fallback shows immediately.
<head>
<link rel="preload" as="font" type="font/woff2"
href="/fonts/brand-display.woff2" crossorigin>
<link rel="stylesheet" href="/styles.css">
</head>
/* styles.css */
@font-face {
font-family: "Brand Display";
src: url(/fonts/brand-display.woff2) format("woff2");
font-display: swap;
}
h1 { font-family: "Brand Display", system-ui, sans-serif; }Cross-origin LCP font: preconnect + preload
ExampleIf the LCP font is on a separate CDN and you know its exact URL, warm the connection and preload the file. crossorigin is mandatory on both for a font origin.
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
<link rel="preload" as="font" type="font/woff2"
href="https://cdn.example.com/fonts/brand-display.woff2" crossorigin>Don't preload the body font on a headline-LCP page
ExampleA frequent mistake: preloading the body font when the LCP element is the headline. The body font isn't on the LCP critical path, so its preload steals early bytes from the headline font and can push LCP up.
WRONG (two preloads, body font isn't the LCP font): <link rel="preload" as="font" ... href="/fonts/brand-display.woff2" crossorigin> <link rel="preload" as="font" ... href="/fonts/body-text.woff2" crossorigin> RIGHT (only the LCP-element font): <link rel="preload" as="font" ... href="/fonts/brand-display.woff2" crossorigin> /* body-text.woff2 loads normally via @font-face with font-display: optional */
Preload + size-adjust for low-CLS swap
ExamplePreload gets the font there sooner; metric overrides on the fallback make the swap shift-free. Together they improve LCP and CLS.
@font-face {
font-family: "Brand Fallback";
src: local("Arial");
size-adjust: 97%;
ascent-override: 90%;
descent-override: 22%;
}
body { font-family: "Brand", "Brand Fallback", sans-serif; }
/* Preload the real Brand WOFF2 so the swap happens fast and shift-free. */Verify in DevTools after deploy
ExampleConfirm the preload is doing its job: the font appears early in the waterfall and there's no 'not used' warning.
DevTools checklist:
Network → Font → reload (throttled to Slow 4G)
✓ brand-display.woff2 starts EARLY, parallel to HTML
✗ NOT 'preloaded but not used' in Console
Lighthouse (throttled):
LCP before: 2.9 s → after preload: 2.5 s (example delta)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.
LCP element is an image, not text
Preload the image insteadIf the largest paint is a hero image, the font isn't the LCP resource and preloading it won't move LCP. Preload the image (<link rel="preload" as="image">) instead. This tool only builds font preloads, so for image LCP you're in the wrong place — fix the image, then preload a font only if a text element becomes the LCP.
Preloaded URL doesn't match the @font-face fetch
Warning — preloaded but not usedThe single most common reason preload doesn't help: the preloaded URL differs from the actual font request (a ?v= cache-buster on one, a path-casing difference, or a missing crossorigin). The browser downloads the font twice and Chrome logs preloaded ... but not used. Copy the exact URL from the Network tab; the builder always adds crossorigin, so don't strip it.
You preloaded all five fonts
Hurts — early-byte contentionPreloading every font makes them compete for the first bytes, frequently regressing LCP versus preloading just the LCP font. The builder won't stop you — it emits a tag per URL with no cap. Trim to the one font the LCP element uses and let the rest load via @font-face.
font-display: block with a slow font
Invisible text — preload masks but doesn't fixWith block, text stays invisible (FOIT) until the font loads or ~3 s passes. Preload shortens the wait but the text is still blank meanwhile, which is bad for perceived LCP. Pair preload with swap (or fallback/optional) so the fallback renders immediately. Choose with font-display-strategy.
Preloading a TTF/OTF instead of WOFF2
Wasteful — larger early payloadTTF/OTF are roughly twice the bytes of the equivalent WOFF2, so preloading them spends more of your early-byte budget. The builder will emit font/ttf/font/otf tags happily. Convert with ttf-to-woff2 and preload the WOFF2.
Returning visitor, warm cache
Marginal — first-visit is the winOn a warm cache the font is already local, so preload only reuses it a little earlier. The headline benefit is the cold-cache first visit. Don't be discouraged if your own (cached) reloads show little change — test in an incognito/cleared-cache profile under throttling.
Variable font on the LCP path
Supported — preload the one variable WOFF2A variable font is a single file covering many weights/widths. Preload that one WOFF2 and every axis instance benefits from the early fetch — you don't (and shouldn't) preload per-weight static files alongside it. If you froze a static instance with variable-font-freezer, preload the resulting static WOFF2 instead.
Preload tag placed after the CSS
Suboptimal — late discoveryIf the preload sits below the render-blocking stylesheet, the CSS is discovered first and the parallelism shrinks. The generated comment says 'BEFORE the stylesheet' — honour it by placing the tag high in <head>.
Frequently asked questions
How much LCP does preloading a font actually save?
Typically 100–500 ms on a slow (throttled 3G/Slow 4G) cold-cache load, by removing the serial wait between CSS parsing and the font request. On fast connections or warm caches the delta is smaller. The exact number depends on font size, connection, and how late the @font-face would otherwise have been discovered.
Which font should I preload?
Exactly the font the LCP element renders in — usually a hero headline or first paragraph. Find the LCP element in Lighthouse, find its font-family, then copy that font's URL from the Network tab. Preload that one; leave the body, footer, and icon fonts to load normally.
How many fonts should I preload?
Almost always one. Occasionally two if a distinct heading and body font are both above the fold on the LCP path. Three or more makes the fonts compete for the first bytes and usually regresses LCP. The builder has no cap, so restraint is on you.
Will preload help if my LCP element is an image?
No. The font isn't the LCP resource in that case, so preloading it does nothing for LCP. Preload the image with as="image" instead. Only preload a font when a text element is (or you want it to be) the LCP element.
Does preload reduce CLS?
Indirectly — the web font swaps in earlier, so the layout settles sooner. But the swap itself can still cause a shift if the fallback and web font have different metrics. Combine preload with size-adjust/ascent-override/descent-override on the fallback to make the swap shift-free.
Why is my LCP unchanged after adding a preload?
Three usual causes: (1) you preloaded the wrong font (not the LCP-element's), (2) the preloaded URL doesn't match the actual fetch so the font is downloaded twice (check for the 'preloaded but not used' console warning), or (3) the LCP element is an image. Verify in the Network tab that the right font fetches early.
Do I still need font-display if I preload?
Yes. Preload controls *when the font starts downloading*; font-display controls *what's shown while it downloads*. With swap, fallback text renders immediately and is replaced when the (now preloaded, so faster) font arrives. Without a good font-display, a slow font can still leave blank text. Pick a value with font-display-strategy.
Should I preload a variable font?
If it's on the LCP path, yes — preload the single variable WOFF2. Because it covers all weights/widths, every instance benefits from the one early fetch. Don't also preload per-weight statics alongside it.
Self-hosted vs Google Fonts — does preload still apply?
Self-hosted is ideal for preload: same-origin, exact URL, no extra host. With Google Fonts loaded the standard way you don't know the font URL up front, so you preconnect to fonts.gstatic.com instead. Self-hosting (generate the CSS with google-fonts-css-generator) lets you preload the specific WOFF2 directly.
Does the builder warn me if I over-preload?
No. It emits one <link rel="preload"> per URL with no cap and no warning. The number-of-fonts discipline is entirely yours — paste only the LCP font (or at most the two LCP-path fonts).
How do I verify the preload is working?
Re-run Lighthouse under throttling on a cleared cache and watch LCP. In the Network tab, the preloaded font should start early, in parallel with the HTML. In the Console, there should be NO 'preloaded but not used' warning. If LCP didn't move and there's no warning, you likely preloaded a non-LCP font.
Is the preloaded font privacy-impacting?
No more than the font itself. Self-hosted fonts are fetched from your own origin, so preloading them leaks nothing extra. For cross-origin fonts, the preload (and its crossorigin) just makes the same request you'd make anyway happen sooner.
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.