Reference guide · performance · Published 2026-08-16 · 4 min read
Font loading best practice
Font loading best practice: font-display swap, preload the LCP font, self-host versus CDN, and subsetting to cut layout shift and LCP.
- ·Why fonts hurt LCP
- ·font-display swap
- ·The loading skeleton
Why fonts sit on the critical path
A web font is a file the browser only needs if a text element uses that family. But while the stylesheet that loads it is usually render-blocking, the font file itself is a late request launched after CSS is parsed. That makes fonts a frequent LCP contribution: the hero heading in the brand font cannot paint until the font file arrives, and a slow font origin can hold the hero hostage.
Two visible symptoms come from the same root: FOIT (invisible text while the font loads, the browser holds the text invisible) and layout shift when the font swaps in at a different width than the fallback (metrics fallback). Both are avoidable with the same tool: the CSS font-display descriptor.
font-display swap
@font-face accepts a font-display descriptor. The values produce different fallback behavior:
| Value | Behavior during load | Use case |
|---|---|---|
auto | Browser decides; often FOIT | The old default, avoid for body text |
block | Invisible text for a short window (max a few seconds), then swap | Display type where the first paint must match the family |
swap | Fallback text immediately, swap when the font arrives | The safe default for most sites |
fallback | Very short FOIT (roughly 100 ms), then fallback; font swap only within a short cap | Body text, tighter consistency |
optional | Uses fallback unless the font is already cached or arrives extremely fast | Decorative text, avoids reflow entirely |
The practical rule: body and general text get swap or fallback; display text that must not reflow (e.g. a big headline) can use block, but the same text should then be measured to avoid layout shift.
With swap, measure the LCP against a simulated slow font to see if the fallback appears first. If the fallback is close in width (a size-adjust on the @font-face, or the new-style fallback metrics), the shift stays under control.
The three rules that win
- Preload the LCP font. Add
<link rel="preload" as="font">for the exact font-face used by the largest heading, withcrossorigin. This moves the download start before CSS parsing. The page-speed preload article explains the sibling pattern for the LCP image. - Serve the subset the text uses. If the page only uses Latin, shipping a full-font + fallback wastes bytes and arrival time on anything heavier. Use Unicode-range subsetting (fonttools / Google Fonts "CSS2 API" splits) and keep the LCP font-file weight under ~30-60 KB.
- Self-host and control the TTFB for the font request. The CDN for static assets article covers the delivery side; LCP is sensitive to the font *first byte*, so preconnect to the font origin (self-hosted or third-party) or self-host critically in the preconnect guide.
The load order
Practical load order for fonts:
- The stylesheet (
font-familydeclarations) is render-blocking, load it with the CSS. - Add
preloadfor the two files that matter (hero font-face viaas="font", plusimagesrcsetwhen defined, the WebP/AVIF compare has the image pairs). - The rest of the family loads afterward via normal CSS; do not preload every weight.
There is one more scaling rule: do not load more than two families. One for headings, one for body, fallbacks for both. A page with six font-faces is a page that shifted multiple times.
Detection and measurement
Test with the DevTools Network panel filtered to font and check font-display timing in the Coverage tab. The two numbers to watch are:
- font download TTFB: should be local/reachable; if not, the static CDN article applies.
- CLS on the fallback swap: run the CLS fix recipe over the swap to confirm whether
size-adjustis needed.
Prevention
Decide the font-display strategy per family (body vs display), preload only the LCP font, and keep the family count small. Recheck after any theme or builder update, because the pattern "now 5 font files load" typically outweighs every other font decision. The web performance audit lives in the same family and will want to see the font request count on the LCP chart, so keep it low.