Font Optimization
Next.js provides automatic font optimization through next/font. It eliminates layout shift and improves privacy by self-hosting fonts.
The Problem with Traditional Fonts
<!-- ❌ Problems with external fonts: -->
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet">
<!-- - Extra network request to Google
- Flash of unstyled text (FOUT)
- Layout shift when fonts load
- Privacy concerns (Google tracking) -->
Using next/font
Google Fonts
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
)
}
What Next.js does:
- Downloads font at build time
- Self-hosts from your domain
- Generates optimal CSS
- Prevents layout shift
Multiple Fonts
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}
Use CSS variables in Tailwind:
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
mono: ['var(--font-roboto-mono)'],
},
},
},
}
<p className="font-sans">Body text in Inter</p>
<code className="font-mono">Code in Roboto Mono</code>
Local Fonts
For custom or licensed 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',
},
],
variable: '--font-myfont',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={myFont.variable}>
<body>{children}</body>
</html>
)
}
Font Configuration Options
const inter = Inter({
// Required for Google Fonts
subsets: ['latin', 'latin-ext'],
// Font weights to include
weight: ['400', '500', '700'],
// Or use variable font with range
weight: 'variable',
// Style variants
style: ['normal', 'italic'],
// CSS variable name (for Tailwind)
variable: '--font-inter',
// Display strategy
display: 'swap', // 'auto' | 'block' | 'swap' | 'fallback' | 'optional'
// Preload (default: true)
preload: true,
// Fallback fonts
fallback: ['system-ui', 'arial'],
// Adjust font metrics to reduce CLS
adjustFontFallback: true,
})
Per-Component Fonts
Apply fonts to specific components:
import { Playfair_Display } from 'next/font/google'
const playfair = Playfair_Display({
subsets: ['latin'],
weight: ['400', '700'],
})
export function Heading({ children }) {
return (
<h1 className={playfair.className}>
{children}
</h1>
)
}
Best Practices
1. Limit 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', '600', '700'],
})
2. Use Variable Fonts When Available
// Variable fonts include all weights in one file
const inter = Inter({
subsets: ['latin'],
// No weight specified = variable font
})
3. Subset for Non-Latin Languages
const notoSans = Noto_Sans_JP({
subsets: ['latin'],
weight: ['400', '700'],
// Japanese character subset is large - only include if needed
})
Summary
- Use
next/fontto self-host fonts automatically - Fonts are downloaded at build time - no runtime requests
- Use
variableprop for CSS custom properties - Prefer WOFF2 format for local fonts
- Only include the weights and subsets you need
- Variable fonts are more efficient than multiple static weights

