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

ValueBehaviorUse case
autoBrowser decides. Usually 3s invisible text (FOIT)Never use explicitly
swapShow fallback immediately, swap when loaded (FOUT)Default for most fonts
fallbackShort block (~100ms), short swap (~3s), then permanent fallbackNon-critical fonts
optionalBrief block; use cached font or permanently fall backSlow 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.

WebfontFallbacksize-adjustascent-overridedescent-override
InterArial107%90%22%
Source Sans ProArial110%88%22%
Source Serif ProGeorgia103%89%24%
RobotoArial100%92%22%
Open SansArial105%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.