Optimize Font Loading: Eliminate Invisible Text
Custom web fonts can delay text rendering for seconds. Learn how to use font-display, preloading, and font subsetting to show text instantly.
Web fonts make your site look beautiful, but they can also make it look broken — showing invisible text or flickering as fonts swap. Here's how to load fonts without compromising performance.
Why Font Loading Matters
- FOIT blocks content — invisible text = invisible content
- Fonts are render-blocking — browsers may wait up to 3 seconds for a font before showing fallback
- Multiple font files — a font family with Regular, Bold, and Italic is 3+ downloads
- Affects FCP and LCP — text elements are often the LCP element
How Lighthouse Flags Font Issues
Two main audits:
- "Ensure text remains visible during webfont load" — flags
@font-facerules withoutfont-display - "All text remains visible during webfont loads" — checks that no text is invisible while fonts load
How to Fix
1. Add font-display: swap
The simplest and most impactful fix:
@font-face {
font-family: "Brand";
src: url("/fonts/brand-regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap; /* Show fallback immediately */
}
@font-face {
font-family: "Brand";
src: url("/fonts/brand-bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
Font-Display Options
| Value | Behavior | Best For |
|---|---|---|
swap |
Show fallback immediately, swap when ready | Body text, headings |
optional |
Use custom font only if already cached or loads in 100ms | Decorative fonts |
fallback |
100ms invisible, then fallback, swap within 3s | Balance of speed and design |
block |
Up to 3s invisible, then fallback | Icon fonts only |
auto |
Browser decides (often block) |
Never use this |
2. Preload Critical Fonts
Tell the browser to start downloading fonts immediately:
<head>
<!-- Preload only above-the-fold fonts -->
<link rel="preload"
href="/fonts/brand-regular.woff2"
as="font"
type="font/woff2"
crossorigin>
<!-- Don't preload fonts only used below the fold -->
</head>
⚠️ Important: The crossorigin attribute is required even for same-origin fonts.
3. Use WOFF2 Format
WOFF2 compresses 30% better than WOFF and has 97%+ browser support:
@font-face {
font-family: "Brand";
src: url("/fonts/brand.woff2") format("woff2"),
url("/fonts/brand.woff") format("woff"); /* fallback */
font-display: swap;
}
4. Subset Your Fonts
Remove characters you don't use:
# Using pyftsubset (fonttools)
pip install fonttools brotli
# Subset to Latin characters only
pyftsubset Brand-Regular.ttf \
--output-file=Brand-Regular-Latin.woff2 \
--flavor=woff2 \
--layout-features='kern,liga' \
--unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD"
Before: Full font = 200KB After: Latin subset = 15-25KB
5. Match Fallback Font Metrics
Reduce layout shift when fonts swap by matching your fallback font's metrics:
/* Adjusted fallback font to match custom font metrics */
@font-face {
font-family: "Brand Fallback";
src: local("Arial");
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
size-adjust: 105%;
}
body {
font-family: "Brand", "Brand Fallback", Arial, sans-serif;
}
Next.js does this automatically with next/font:
import { Inter } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap",
});
export default function Layout({ children }) {
return <body className={inter.className}>{children}</body>;
}
6. Self-Host Google Fonts
Google Fonts adds an extra DNS lookup and render-blocking request. Self-hosting is faster:
# Download with google-webfonts-helper
# https://gwfh.mranftl.com/fonts
# Or use next/font (auto-downloads and self-hosts)
import { Roboto } from "next/font/google";
const roboto = Roboto({
weight: ["400", "700"],
subsets: ["latin"],
display: "swap",
});
7. Limit Font Variants
Each weight/style combination is a separate file:
/* ❌ BAD — 6 font files to download */
@font-face { font-weight: 300; /* Light */ }
@font-face { font-weight: 400; /* Regular */ }
@font-face { font-weight: 500; /* Medium */ }
@font-face { font-weight: 600; /* Semi-bold */ }
@font-face { font-weight: 700; /* Bold */ }
@font-face { font-weight: 400; font-style: italic; }
/* ✅ GOOD — 2 font files */
@font-face { font-weight: 400; /* Regular */ }
@font-face { font-weight: 700; /* Bold */ }
Use CSS font-synthesis for italic if needed instead of loading a separate italic file.
Quick Wins Checklist
- Add
font-display: swapto all@font-facerules - Preload 1-2 critical font files used above the fold
- Use WOFF2 format exclusively (with WOFF fallback)
- Subset fonts to include only characters you use
- Self-host fonts instead of loading from Google Fonts CDN
- Limit to 2-3 font weight/style variants
- Use
next/fontin Next.js for automatic optimization
Ready to stop wasting ad spend?
Track your landing page performance, monitor Core Web Vitals, and calculate exactly how much slow pages cost you.
Start Free — No Credit Card