Web Fonts Are Killing Your LCP (And What to Do About It)
Here’s a scenario that plays out on thousands of websites.
A designer picks a beautiful typeface from Google Fonts or a type foundry. A developer adds the <link> tag to the <head>. The site looks polished and on-brand. Lighthouse comes back with an LCP of 4.2 seconds.
The culprit? The font.
How Font Loading Breaks LCP
Largest Contentful Paint (LCP) measures when the biggest visible element in the viewport finishes rendering. For most marketing pages, that element is a headline — a text node.
Here’s the problem: browsers don’t render text until the font is loaded. If your headline uses a custom typeface and that font hasn’t arrived yet, the browser has two choices:
- FOIT (Flash of Invisible Text) — show nothing until the font loads
- FOUT (Flash of Unstyled Text) — show the text in a fallback font, then swap
Either way, your LCP element is not painted until the custom font is available. If the font loads slowly — because it’s hosted on an external server, or because it’s a large file, or because the browser didn’t know to fetch it early — your LCP suffers.
The Cascade Problem
This is where it gets worse. Fonts are typically declared in CSS:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
}
Before the browser can fetch the font, it has to:
- Download and parse your HTML
- Find the
<link>to your stylesheet - Download and parse your CSS
- Discover the
@font-facedeclaration - Finally request the font file
That’s four steps before the font download even begins. On a slow connection, each step adds meaningful time. By the time the font arrives, your LCP window has closed.
Fix 1: Preload Your Critical Font
Tell the browser about the font before it parses the stylesheet:
<head>
<link rel="preload" href="/fonts/inter-regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/styles.css">
</head>
rel="preload" moves the font fetch to the highest priority, parallel to HTML parsing. The font arrives earlier, and your headline renders sooner.
Note the crossorigin attribute — it’s required even for self-hosted fonts, because fonts use CORS.
Fix 2: Use font-display: optional or swap
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-display: optional;
}
swap— show fallback text immediately, swap to custom font when ready. Eliminates FOIT, but causes visual shift.optional— give the font a very short window (100ms). If it doesn’t arrive, use the fallback for the rest of the session. No swap, no shift. Best for LCP.
For your LCP headline, font-display: optional combined with a well-matched system font fallback is often the best choice.
Fix 3: Self-Host Your Fonts
Google Fonts, Typekit, and other hosted font services add a DNS lookup and connection to an external server. Even with rel="preconnect", this adds latency.
Serving fonts from your own domain eliminates the external dependency entirely. Tools like google-webfonts-helper make it easy to download and self-host any Google Font with the correct @font-face declarations.
Fix 4: Match Your Fallback Font
Even with font-display: swap, a poor fallback font causes layout shift when the custom font loads — because different fonts have different metrics (line height, letter spacing, x-height).
Modern CSS gives you tools to fix this:
@font-face {
font-family: 'Inter-fallback';
src: local('Arial');
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
size-adjust: 107%;
}
body {
font-family: 'Inter', 'Inter-fallback', sans-serif;
}
By adjusting the fallback font’s metrics to match your custom font, the swap becomes visually seamless. No layout shift. No CLS hit.
The Broader Pattern
Fonts are one specific example of a general problem: resources that block rendering but aren’t discovered until late in the loading cascade.
The same pattern applies to hero images, CSS background images, and scripts that render content. The fix is always the same — surface the dependency earlier, either through preloading or by reducing the number of steps between HTML parse and resource fetch.
Your LCP element deserves to load with the same priority as everything else visible above the fold. If it’s waiting on a font, that font should be the first thing you fetch.