Impact: MEDIUM (eliminates font-related layout shift and removes external network dependency)
Loading fonts from Google Fonts or other CDNs requires DNS lookups, TCP connections, and TLS handshakes to external servers — adding 100-300ms to font loading. During this time, text either flashes from a fallback font (FOUT) or is invisible (FOIT), both causing Cumulative Layout Shift.
next/font downloads fonts at build time, self-hosts them from your own domain, and automatically generates size-adjust CSS to match the fallback font's metrics — eliminating layout shift entirely.
Incorrect (external font loading):
// ❌ External CSS link in layout — render-blocking, causes CLS// app/layout.tsxexport default function RootLayout({ children }) { return ( <html> <head> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" /> </head> <body style={{ fontFamily: 'Inter, sans-serif' }}>{children}</body> </html> )}
/* ❌ @import in CSS — blocks rendering until font CSS is downloaded */@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');body { font-family: 'Inter', sans-serif;}
// ❌ Self-hosted but without size-adjust — still causes layout shift<style>{` @font-face { font-family: 'CustomFont'; src: url('/fonts/custom.woff2'); font-display: swap; /* Text visible immediately, but shifts when font loads */ }`}</style>
Correct (next/font with automatic optimization):
// app/layout.tsx// ✅ Google Fonts — downloaded at build time, self-hostedimport { Inter } from 'next/font/google'const inter = Inter({ subsets: ['latin'], display: 'swap', // Automatically generates size-adjust for zero CLS})export default function RootLayout({ children }) { return ( <html lang="en" className={inter.className}> <body>{children}</body> </html> )}
// ✅ Multiple weights and variable fontsimport { Inter, Fira_Code } from 'next/font/google'const inter = Inter({ subsets: ['latin'], variable: '--font-inter', // CSS variable for Tailwind})const firaCode = Fira_Code({ subsets: ['latin'], variable: '--font-mono',})export default function RootLayout({ children }) { return ( <html lang="en" className={`${inter.variable} ${firaCode.variable}`}> <body>{children}</body> </html> )}