font-display and Web Font Optimization: A Practical Guide to Better Core Web Vitals
How Web Fonts Affect Performance
Web fonts (Google Fonts, Adobe Fonts, etc.) improve design quality but can significantly hurt your Core Web Vitals scores if not configured properly.
The two main impacts are:
- LCP (Largest Contentful Paint) degradation: Slow font loading blocks text rendering, delaying the largest content element
- CLS (Cumulative Layout Shift) increase: Font swapping causes text to reflow when the web font replaces the fallback
Both problems can be largely eliminated with the correct font-display setting.
What Is font-display?
font-display is a CSS property that controls how the browser renders text while a web font is loading.
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Regular.woff2') format('woff2');
font-display: swap;
}
The Five font-display Values
| Value | Behavior | LCP Impact | CLS Impact |
|---|---|---|---|
auto | Browser default (usually same as block) | Bad | Low |
block | Invisible text until font loads (up to 3s) | Bad | Low |
swap | Show fallback immediately, swap when loaded | Good | Slightly higher |
fallback | 100ms invisible, then fallback, short swap window | Good | Low |
optional | 100ms invisible, use font only if already cached | Best | Lowest |
Google's Recommendation
Google recommends font-display: swap. PageSpeed Insights and Lighthouse flag pages without it: "Ensure text remains visible during webfont load."
The key benefit of swap is that text is visible immediately, even before the web font loads. This dramatically improves LCP on text-heavy pages — which includes most blogs, documentation sites, and corporate websites.
The tradeoff is a potential minor CLS when the browser swaps from the fallback font to the web font. This can be mitigated with size-adjust (covered below).
Optimizing Google Fonts Loading
Step 1: Add display=swap
Simply append &display=swap to your Google Fonts URL:
<!-- Bad: no display parameter -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700" rel="stylesheet">
<!-- Good: display=swap added -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
Step 2: Add preconnect Hints
Pre-establish connections to Google Fonts domains to reduce latency:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
Step 3: Self-Host for Maximum Speed
Hosting font files on your own server eliminates external requests and gives you full cache control:
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
In Next.js, the next/font module automatically self-hosts fonts at build time with optimal font-display settings:
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
weight: ['400', '700'],
display: 'swap',
});
Preventing CLS with size-adjust
When using font-display: swap, CLS occurs because the fallback font and web font have different metrics (character widths, heights, line spacing). The size-adjust property lets you tune the fallback to match:
@font-face {
font-family: 'Adjusted Arial';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: 'Inter', 'Adjusted Arial', sans-serif;
}
Calculating these values manually is tedious. Use these tools instead:
- Fontaine — integrates with Next.js and Nuxt
- next/font — automatically applies
size-adjustfor Google Fonts
Minimize Font Weights
Every font weight you load adds to the download size and delays LCP.
<!-- Bad: loading 9 weights you don't need -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<!-- Good: only the weights you actually use -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
Rule of thumb: Most sites need only 2-3 weights (Regular + Bold, optionally Medium).
Preloading Critical Fonts
For fonts displayed above the fold, preloading tells the browser to start downloading immediately:
<link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin>
Warning: Only preload fonts you actually use in the initial viewport. Preloading unused fonts wastes bandwidth and can hurt performance.
Measuring the Impact
Verify your font optimizations with these tools:
- PageSpeed Insights — check LCP and CLS scores; confirm "Ensure text remains visible during webfont load" warning is resolved
- Chrome DevTools > Performance — visualize font loading in the timeline
- Chrome DevTools > Network — inspect font file sizes and load times
- IndexReady — enter any URL to get automated Core Web Vitals scoring and performance analysis
Frequently Asked Questions
Should I use font-display: swap or optional?
Use swap when brand consistency matters — it guarantees your web font will eventually display. Use optional when performance is the absolute priority — it may skip the web font entirely on slow connections, producing zero CLS. For most websites, swap is the right balance.
Does next/font handle font-display automatically?
Yes. next/font defaults to font-display: swap, self-hosts the font files at build time, and automatically calculates size-adjust values to minimize CLS. For Next.js projects, using next/font is the easiest and most effective approach.
Are CJK (Chinese/Japanese/Korean) fonts heavier than Latin fonts?
Significantly. CJK fonts contain thousands of glyphs, making their file sizes 10-20x larger than Latin fonts. Google Fonts serves CJK fonts in optimized subsets, but minimizing the number of weights is still critical. Loading a single Japanese font weight can exceed 1MB without subsetting.
Should I preload all my fonts?
No. Only preload fonts that render above the fold on initial page load. Preloading too many fonts competes for bandwidth with other critical resources like images and CSS, potentially making performance worse. Typically, preload one or two font files at most.
How do I check if font-display is working?
Open Chrome DevTools, go to the Network tab, and filter by "Font." Reload the page and observe: with swap, text should appear immediately in a system font, then switch to the web font once loaded. You can also throttle the network to "Slow 3G" to make the swap behavior more visible.