Font Loading Strategy Guide
Complete guide to loading web fonts without hurting Core Web Vitals. Covers preload, font-display, fallback stacks, metric overrides, and browser behaviour for every strategy.
The three levers of font loading
Font loading performance is controlled by three CSS and HTML mechanisms. Getting any of them wrong silently degrades LCP or CLS:
- Preload — tells the browser to fetch the font in parallel with CSS, saving 100–500ms.
- font-display — controls what is shown before the font arrives (fallback vs invisible).
- Fallback stack — determines what users see if the font never loads or loads slowly.
Recommended pattern for most sites
<head>
<link rel="preconnect" href="https://example.com">
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/css/main.css">
</head>
@font-face {
font-family: "Main";
src: url("/fonts/main.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
body {
font-family: "Main", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
Preload rules
- Preload only fonts used in the first rendered view — usually one font, occasionally two.
- Always include
crossorigin— fonts are fetched in CORS mode. - Include correct MIME type (
type="font/woff2") so the browser deduplicates the request. - Place preload tags before any stylesheet link in
<head>. - Do not preload Google Fonts URLs — the hashed URLs change, making stable preload impossible. Self-host instead.
font-display decision matrix
| Content type | Recommended font-display | Rationale |
|---|---|---|
| Body text | swap | Reading must never be blocked |
| Headlines (LCP element) | swap | Fallback renders instantly, swaps when font arrives |
| Hero image overlay text | optional or swap with adjusted fallback | Zero CLS or fast enough swap |
| Icon fonts | block | Fallback glyphs render broken shapes |
| Decorative display fonts | optional | OK if slow-connection users never see them |
Metric-adjusted fallbacks (advanced)
Fallback fonts have different metrics than your custom font, causing layout shift when the font swaps in. Modern CSS provides metric override properties that reduce this shift significantly:
@font-face {
font-family: "Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: "Main", "Fallback", sans-serif;
}
These properties adjust the fallback's rendering to match the primary font. The result: when the font swaps in, text barely shifts. This is the most effective technique for reducing CLS caused by late font swaps.
Self-host vs CDN
| Aspect | Self-host | Google Fonts CDN |
|---|---|---|
| DNS lookups | 1 (your domain) | 2 (fonts.googleapis.com + fonts.gstatic.com) |
| Preload support | Yes — stable URLs | Difficult — hashed URLs change |
| Caching | You control TTL | Google controls TTL |
| GDPR compliance | Full control | Requires consent in EU |
| Setup cost | Higher | Zero |
Frequently asked questions
What is the fastest way to load web fonts?
One preloaded WOFF2 variable font (100–200KB), loaded via <link> in <head> with font-display: swap, plus a metric-adjusted fallback. Avoid @import entirely.
Should I self-host or use Google Fonts CDN?
Self-hosting is generally faster (one DNS lookup instead of two), enables stable preload URLs, and gives you GDPR compliance. Use CDN only for quick prototypes.
What is FOUT and why does it matter?
FOUT (Flash of Unstyled Text) means users briefly see fallback text before the custom font arrives. It is universally preferable to FOIT (Flash of Invisible Text) because it does not block reading.
How much does preload actually save?
Typical savings are 100–500ms of LCP improvement by fetching the font in parallel with CSS instead of after CSS parsing completes.
Can I preload all my fonts?
No. Preloading multiple fonts competes with other critical resources. Preload only the fonts used in the first rendered view.