IAnswerable

Load Web Fonts Without the Flash

19 Jan 2026

That half-second flash of unstyled text, followed by a jarring swap to your custom font, is the most fixable performance annoyance on the web. Three changes get rid of it almost entirely.

Self-host your fonts

Google Fonts via <link href="https://fonts.googleapis.com/..."> adds a third-party DNS lookup, connection, and round trip before the font file even starts downloading. Self-hosting removes all of it — the font ships from the same origin as everything else, with no extra connection setup.

# Download the woff2 files you need, then:
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  font-style: normal;
}

Next.js makes this close to automatic with next/font, which self-hosts and subsets Google Fonts at build time — no runtime request to Google at all:

import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'], display: 'swap' })

export default function RootLayout({ children }) {
  return (
    <html className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

Set font-display correctly

font-display controls what happens in the gap between “page rendered” and “font file arrived.” The two options worth knowing:

Value Behavior When to use
swap Show fallback font immediately, swap to custom font whenever it loads Body text, most cases
optional Show fallback; only use custom font if it loads almost immediately (fast connection), otherwise keep the fallback Decorative or non-critical fonts where a swap-induced layout shift is worse than skipping the custom font

swap is the right default for most sites — it never leaves users staring at invisible text (that’s what font-display: block, the old default, does, and it’s a straightforward LCP hit if your LCP element is text).

Preload the font that matters most

Preloading tells the browser to fetch the font file before it’s discovered through normal CSS parsing — useful for the one font used in above-the-fold text, not for every font on the page:

<link
  rel="preload"
  href="/fonts/inter-var.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

Preload only your primary body/heading font. Preloading every font weight and style you own delays other critical resources — you’re spending the browser’s limited early-loading budget on fonts the user might not even see yet (an italic variant used only in a footer, for example).

Kill the layout shift from font swapping

Even with swap, if your fallback font and custom font have different metrics (character width, line height), the swap moves and reflows text — that’s a CLS hit, not just a visual flash. Match them with size-adjust, ascent-override, and descent-override:

@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
}

next/font calculates and applies these fallback adjustments automatically — one more reason to use it over a raw @font-face if you’re on Next.js. If you’re hand-rolling this, Fontaine (a Vite/webpack plugin) or the capsize tool can generate the right override values for a given font pair.

Subset to what you actually use

If your site is English-only, you don’t need Cyrillic, Greek, and full CJK glyph coverage shipping in every font file. Subsetting a full Inter variable font to latin alone can cut the file from several hundred KB to well under 50KB.

const inter = Inter({ subsets: ['latin'], display: 'swap' })

next/font and most font-subsetting tools (glyphhanger, fonttools) handle this without you manually editing font files.

The takeaway

Self-host (or use next/font), set font-display: swap, preload only the primary font, and match fallback metrics to kill the layout shift. That combination removes both the visible flash and the CLS penalty — do all four, not just one.