Font Optimization
Optimize web fonts for faster loading and zero layout shift
Web fonts can significantly impact performance, causing layout shifts (CLS) and delaying text rendering. Next.js provides built-in font optimization through next/font.
The Problem with Web Fonts
Without optimization, fonts cause:
- FOIT (Flash of Invisible Text): Text hidden until font loads
- FOUT (Flash of Unstyled Text): Fallback font replaced by web font
- CLS: Layout shifts when fonts swap
- Slow LCP: If text is the LCP element
Using next/font
Google Fonts
import { Inter, Roboto_Mono } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
});
export default function RootLayout({ children }) {
return (
<html className={`${inter.variable} ${robotoMono.variable}`}>
<body className={inter.className}>{children}</body>
</html>
);
}Local Fonts
import localFont from 'next/font/local';
const myFont = localFont({
src: [
{
path: './fonts/MyFont-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: './fonts/MyFont-Bold.woff2',
weight: '700',
style: 'normal',
},
],
display: 'swap',
variable: '--font-my-font',
});Variable Fonts
import { Inter } from 'next/font/google';
// Variable font - single file, all weights
const inter = Inter({
subsets: ['latin'],
display: 'swap',
// Variable fonts support weight ranges
weight: 'variable', // or specific: ['400', '500', '700']
});Font Display Strategies
const font = Inter({
subsets: ['latin'],
display: 'swap', // Recommended for most cases
});| Value | Behavior | Use Case |
|---|---|---|
swap | Show fallback, then swap | Most content |
optional | Use fallback if slow | Non-critical text |
block | Brief invisible, then font | Brand text |
fallback | Brief invisible, then fallback | Compromise |
auto | Browser decides | Avoid |
Preventing Layout Shift
adjustFontFallback
const inter = Inter({
subsets: ['latin'],
adjustFontFallback: true, // Adjusts fallback metrics
});This generates CSS that adjusts the fallback font to match the web font's metrics:
/* Generated automatically */
@font-face {
font-family: '__Inter_Fallback';
src: local('Arial');
ascent-override: 90.49%;
descent-override: 22.56%;
line-gap-override: 0%;
size-adjust: 107.06%;
}Custom Fallback Metrics
const myFont = localFont({
src: './fonts/MyFont.woff2',
fallback: ['system-ui', 'arial'],
adjustFontFallback: 'Arial', // Base metrics on Arial
});Subsetting
Only include characters you need:
// Latin only (smaller file)
const inter = Inter({
subsets: ['latin'],
});
// Multiple subsets if needed
const notoSans = Noto_Sans({
subsets: ['latin', 'cyrillic', 'greek'],
});| Subset | Size Impact |
|---|---|
| latin | Base size |
| latin-ext | +5-10% |
| cyrillic | +10-20% |
| All subsets | 2-3x base |
Preloading Critical Fonts
// next/font automatically preloads
// For manual preloading:
export default function RootLayout({ children }) {
return (
<html>
<head>
<link
rel="preload"
href="/fonts/MyFont.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
</head>
<body>{children}</body>
</html>
);
}CSS Variables for Flexibility
// Define fonts with CSS variables
const inter = Inter({
subsets: ['latin'],
variable: '--font-sans',
});
const mono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-mono',
});
// In layout
<html className={`${inter.variable} ${mono.variable}`}>
// In CSS/Tailwind
// tailwind.config.js
module.exports = {
theme: {
fontFamily: {
sans: ['var(--font-sans)', 'system-ui'],
mono: ['var(--font-mono)', 'monospace'],
},
},
};Performance Comparison
| Approach | WOFF2 Size | CLS | FCP Impact |
|---|---|---|---|
| No optimization | 100KB+ | 0.1-0.3 | +500ms |
| next/font Google | 20-40KB | ~0 | +50ms |
| next/font local | Variable | ~0 | +20ms |
| System fonts only | 0KB | 0 | 0ms |
Font Loading Strategies
Critical vs Non-Critical
// Critical fonts - load immediately
const headingFont = Inter({
subsets: ['latin'],
display: 'swap',
preload: true,
});
// Non-critical - can load later
const codeFont = Roboto_Mono({
subsets: ['latin'],
display: 'optional',
preload: false,
});Reduce Font Weights
// ❌ Loading all weights
const inter = Inter({
subsets: ['latin'],
weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
});
// ✅ Only what you need
const inter = Inter({
subsets: ['latin'],
weight: ['400', '500', '700'],
});System Font Stack
For maximum performance, consider system fonts:
font-family:
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;No download required, instant rendering, zero CLS.
Debugging Font Issues
Check Font Loading
// In browser console
document.fonts.ready.then(() => {
console.log('Fonts loaded:', document.fonts.size);
document.fonts.forEach(font => {
console.log(font.family, font.weight, font.status);
});
});Font Loading Timeline
// Monitor font loading performance
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.initiatorType === 'css' && entry.name.includes('font')) {
console.log('Font loaded:', entry.name, entry.duration);
}
}
});
observer.observe({ entryTypes: ['resource'] });Checklist
Setup
Use next/font for all fonts Automatic optimization, self-hosting, and zero CLS.
Enable adjustFontFallback Matches fallback metrics to prevent layout shift.
Set display: 'swap' Show fallback immediately while font loads.
Use CSS variables Flexible theming with --font-sans and --font-mono.
Optimization
Subset to needed characters Only include Latin if you don't need Cyrillic.
Limit font weights Include only weights you actually use.
Use variable fonts when possible Single file for all weights and styles.
Preload critical fonts next/font does this automatically.
Performance
Check CLS in DevTools Layout Shift Regions highlight font-related shifts.
Verify font-display behavior Check Network tab for font load timing.
Measure font load time Use Performance panel to see font download duration.
Consider system fonts for speed Zero download, instant rendering.