All Insights
PerformanceJanuary 20265 min read

Font loading: the invisible performance problem

Custom web fonts can add 300–900ms to your render time and cause significant layout shift. Here's how to load them without paying the penalty.

PerformanceTypographyFrontend

Web fonts are the most commonly overlooked performance problem on otherwise well-optimized sites. Teams spend hours compressing images and deferring scripts, then serve a 400KB custom font that blocks text rendering for 600ms. The font is invisible in most performance audits because it doesn't show up in the same way as image weight — but it shows up in LCP and CLS scores every time.

There are three behaviors the browser can take when a font isn't ready: wait (FOIT — Flash of Invisible Text), show a fallback font and swap when the custom font loads (FOUT — Flash of Unstyled Text), or show the fallback indefinitely if the custom font doesn't load in time. Each behavior has costs. Understanding them is the foundation of a performant font strategy.

The font-display property

Font-display controls the browser's fallback behavior. The default behavior for Google Fonts and most self-hosted fonts is either block (FOIT) or auto (browser-dependent). The right choice for almost every web property is font-display: swap — which shows the fallback immediately and swaps when the custom font is ready.

  • font-display: auto — browser default, often causes FOIT
  • font-display: block — waits up to 3s for the font, shows nothing until it loads
  • font-display: swap — shows fallback immediately, swaps when font is ready (causes visible reflow)
  • font-display: fallback — 100ms invisible period, then fallback, then swap only if font loads quickly
  • font-display: optional — shows fallback, only uses custom font if it's already cached

font-display: optional is the highest-performance option but means first-time visitors always see the fallback. It's the right choice for body text. For hero display type it's usually too aggressive.

Preloading critical fonts

The fastest way to reduce the visible cost of font loading is to preload the fonts that will be used above the fold. A preload link in the document head tells the browser to fetch the font resource at the highest priority, before it would normally encounter the font in the CSS.

Only preload fonts that are used in the critical path — the first screenful of content. Preloading a font that isn't needed until below the fold wastes bandwidth and can actually delay the LCP element if it competes with the hero image for network priority.

340ms
average LCP improvement from implementing proper font preloading

Fallback font calibration

When font-display: swap causes a FOUT, the layout shift is driven by the difference in metrics between the custom font and the fallback. If the fallback is Arial and the custom font is a condensed display face, the shift can be dramatic. The solution is to calibrate the fallback using CSS font-size-adjust, line-height, and letter-spacing to make the fallback's metrics as close to the custom font as possible.

Next.js's next/font handles this automatically for Google Fonts and local fonts — it generates optimized CSS that minimizes CLS during font swap. If you're on Next.js and not using next/font, you're leaving the most accessible performance improvement in the framework unused.

Next Article

JavaScript bundle size: what to cut before you optimize anything else

Performance · 6 min read

Read Next