Font loading
Taming the flash of invisible text without slowing the page
Every @font-face declaration needs font-display: swap. Without it, the browser defaults to three seconds of invisible text.
The performance pipeline
1. Preconnect to the font origin
2. Preload critical fonts
Preload only above-the-fold fonts. The crossorigin attribute is required even for same-origin fonts. Omit it and the browser downloads the font twice.
Missing crossorigin on a font preload means the browser downloads the font
twice. The preload is silently wasted.
3. Choose a font-display strategy
| Value | Behavior | Use case |
|---|---|---|
auto | Browser decides. Usually 3s invisible text (FOIT) | Never use explicitly |
swap | Show fallback immediately, swap when loaded (FOUT) | Default for most fonts |
fallback | Short block (~100ms), short swap (~3s), then permanent fallback | Non-critical fonts |
optional | Brief block; use cached font or permanently fall back | Slow networks, progressive enhancement |
swap is the right default.
Taming the flash (FOUT)
font-display: swap trades invisible text for a visible flash when the webfont arrives. Tune the fallback to match the webfont's metrics with size-adjust, ascent-override, and descent-override:
Fontaine and next/font automate this calculation.
| Webfont | Fallback | size-adjust | ascent-override | descent-override |
|---|---|---|---|---|
| Inter | Arial | 107% | 90% | 22% |
| Source Sans Pro | Arial | 110% | 88% | 22% |
| Source Serif Pro | Georgia | 103% | 89% | 24% |
| Roboto | Arial | 100% | 92% | 22% |
| Open Sans | Arial | 105% | 90% | 22% |
These are approximations. Use Fontaine or next/font for exact values calculated from font metrics.
Common mistakes
No font-display at all. Defaults to auto, three seconds of invisible text.
Preloading every weight. Preload only above-the-fold weights.
Loading weights you don't use. If the design uses 400 and 600, don't load 300, 500, 700, 800, 900.
Google Fonts without preconnect. Preconnect to both fonts.googleapis.com and fonts.gstatic.com, or self-host.
Font subsetting
unicode-range subsetting tells the browser to download a font file only when the page uses characters in that range:
Google Fonts does this automatically, splitting each font into language-specific slices. Self-hosting? Use glyphhanger or subfont to generate subsets matching the characters you use.
Skip subsetting for variable fonts where it breaks axis interpolation, or sites with user-generated content in unpredictable scripts.
Open your site's DevTools Network tab. Filter by "font". Count the font files.
Are any uncached? Are you loading weights you never use? Add font-display: swap to any declaration missing it.