As web developers, have you encountered these font-related challenges:
- Layout shifts during font loading
- Slow Google Fonts loading in certain regions
- Large font file sizes impacting performance
- Complex font management across components
Next.js 13+ comes with a built-in next/font
component that elegantly solves these problems. Let's dive deep into this powerful font solution.
Understanding Layout Shift in Font Loading
Before exploring next/font
, let's understand why layout shifts occur during font loading.
The Root Cause
Consider this example where three text elements have the same font size but different fonts:
.text1 { font-family: "Arial"; font-size: 64px; }
.text2 { font-family: "Times New Roman"; font-size: 64px; }
.text3 { font-family: "Georgia"; font-size: 64px; }
Actual rendering heights:
- Arial: 71px actual height
- Times New Roman: 84px actual height
- Georgia: 79px actual height
This difference in actual rendering heights is why elements "jump" when custom fonts load and replace system fonts, even though the font-size
is identical.
Traditional Solutions and Their Limitations
Traditional approaches include:
font-display: swap
- Controls font switching but doesn't prevent shifts- Font preloading - Speeds up loading but shifts still occur
- FOUT (Flash of Unstyled Text) - Compromises user experience
The next/font Innovation
next/font employs an ingenious solution using CSS size-adjust
. Let's examine the implementation:
Before Compilation
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin']
})
export default function Layout({ children }) {
return (
<html className={inter.className}>
<body>{children}</body>
</html>
)
}
After Next.js Compilation
/* Generated CSS */
@font-face {
font-family: '__Inter_123456';
src: url('/_next/static/media/inter.woff2') format('woff2');
/* Other properties */
}
/* The magic: Fallback font adjustment */
@font-face {
font-family: '__Inter_Fallback_123456';
src: local("Arial");
ascent-override: 90.20%;
descent-override: 22.48%;
line-gap-override: 0.00%;
size-adjust: 107.40%
}
/* Generated class */
.className_123456 {
font-family: '__Inter_123456', '__Inter_Fallback_123456';
}
The magic happens through:
- Creation of two font declarations: target font and adjusted fallback
- Precise metric adjustments using
size-adjust
and other properties - Perfect size matching between fallback and final fonts
Result comparison:
- Traditional: Visible text jumps during loading
- next/font: Seamless transition, zero movement
next/font Usage Guide
1. Using Google Fonts
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin']
})
export default function Layout({ children }) {
return (
<html className={inter.className}>
<body>{children}</body>
</html>
)
}
Generated HTML:
<html class="__className_123456">
<body>
<p>Hello World</p>
</body>
</html>
Generated CSS:
/* Auto-optimized font code */
@font-face {
font-family: '__Inter_123456';
/* Font file downloaded and optimized at build time */
src: url('/_next/static/media/inter-optimized.woff2') format('woff2');
font-display: swap;
}
2. Using Local Fonts
import localFont from 'next/font/local'
const myFont = localFont({
src: './my-font.woff2'
})
After compilation:
@font-face {
font-family: '__myFont_123456';
src: url('/_next/static/media/my-font-optimized.woff2') format('woff2');
/* Auto-added optimization properties */
ascent-override: 90%;
descent-override: 23%;
line-gap-override: 0%;
size-adjust: 105%;
}
Advanced Techniques: Variable Fonts and Performance
Variable fonts are revolutionary, allowing multiple weight and style variations in a single file. Next.js recommends them because they:
- Reduce font file count
- Enable smooth weight transitions
- Improve overall performance
Example of variable font usage:
const roboto = Roboto({
weight: '100 900', // Supports all weights from 100 to 900
subsets: ['latin']
})
Best Practice Case Study: Building a Bilingual Responsive Page
Let's demonstrate a real-world implementation of font optimization in a bilingual (English/Chinese) Next.js project using:
- Noto Sans SC for Chinese
- Inter for English
- Tailwind CSS for responsive design
1. Font Configuration
// fonts/index.js
import { Inter } from 'next/font/google'
import { Noto_Sans_SC } from 'next/font/google'
// English font config
export const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
// Preload all weights
weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
})
// Chinese font config
export const notoSansSC = Noto_Sans_SC({
subsets: ['latin'],
weight: ['100', '300', '400', '500', '700', '900'],
display: 'swap',
variable: '--font-noto-sc',
// Only load necessary characters
preload: true,
})
2. Tailwind Configuration
// tailwind.config.js
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)', 'var(--font-noto-sc)'],
inter: ['var(--font-inter)'],
noto: ['var(--font-noto-sc)'],
},
},
},
}
3. Root Layout
// app/layout.tsx
import { inter, notoSansSC } from '@/fonts'
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={`${inter.variable} ${notoSansSC.variable}`}>
<body className="font-sans">
{children}
</body>
</html>
)
}
4. Typography Components
// components/Typography.tsx
import { FC, PropsWithChildren } from 'react'
import clsx from 'clsx'
interface TypographyProps extends PropsWithChildren {
className?: string
lang?: 'zh' | 'en'
}
export const H1: FC<TypographyProps> = ({
children,
className,
lang = 'en'
}) => {
return (
<h1
className={clsx(
'text-4xl font-bold tracking-tight',
lang === 'en' && 'font-inter',
lang === 'zh' && 'font-noto',
className
)}
>
{children}
</h1>
)
}
export const Paragraph: FC<TypographyProps> = ({
children,
className,
lang = 'en'
}) => {
return (
<p
className={clsx(
'text-base leading-relaxed',
lang === 'en' && 'font-inter tracking-wide',
lang === 'zh' && 'font-noto tracking-normal',
className
)}
>
{children}
</p>
)
}
5. Example Page
// app/page.tsx
import { H1, Paragraph } from '@/components/Typography'
export default function Home() {
return (
<main className="max-w-4xl mx-auto p-6">
<div className="space-y-8">
{/* English content */}
<H1 lang="en" className="mb-4">
Font Optimization Guide
</H1>
<Paragraph lang="en">
Font optimization is crucial for frontend performance. With Next.js font
components, we can easily achieve zero layout shift, automatic font
optimization, and better loading performance.
</Paragraph>
{/* Chinese content */}
<H1 lang="zh" className="mb-4">
字体优化指南
</H1>
<Paragraph lang="zh">
字体优化是前端性能优化中的重要一环。使用 Next.js 的 font
组件,我们可以轻松实现零布局偏移、自动字体优化、更好的加载性能等特性。
</Paragraph>
</div>
</main>
)
}
6. Compiled Output
Next.js generates optimized code:
<!-- Generated HTML -->
<html lang="en" class="__variable_inter __variable_noto">
<body class="font-sans">
<main class="max-w-4xl mx-auto p-6">
<!-- Content -->
</main>
</body>
</html>
/* Generated CSS */
@font-face {
font-family: '__Inter_123456';
src: url('/_next/static/media/inter-optimized.woff2') format('woff2');
font-display: swap;
}
@font-face {
font-family: '__Noto_Sans_SC_123456';
src: url('/_next/static/media/noto-sans-sc-optimized.woff2') format('woff2');
font-display: swap;
}
/* Fallback font optimization */
@font-face {
font-family: '__Inter_Fallback_123456';
src: local("Arial");
ascent-override: 90.20%;
descent-override: 22.48%;
line-gap-override: 0.00%;
size-adjust: 107.40%
}
@font-face {
font-family: '__Noto_Sans_SC_Fallback_123456';
src: local("PingFang SC");
ascent-override: 87.56%;
descent-override: 21.89%;
line-gap-override: 0.00%;
size-adjust: 105.95%
}
7. Benefits of This Approach
-
Performance Optimization:
- Font files downloaded and optimized at build time
- Chinese fonts load only necessary characters
- Automatic fallback handling eliminates layout shift
-
Developer Experience:
- Unified font management through CSS variables
- Reusable typography components
- Automatic language-specific font switching
-
User Experience:
- Zero layout shift
- Fast initial load
- Smooth font transitions
- Optimal display for both English and Chinese
-
Maintainability:
- Centralized font configuration
- Component-based typography
- Clean, organized code structure
Conclusion
The next/font
component represents a significant advancement in web font optimization. By understanding and implementing these techniques, you can achieve:
- Zero layout shift during font loading
- Better performance and privacy
- Flexible font management
- Seamless integration with modern development tools
I hope this guide helps you better understand and implement font optimization in your Next.js projects. If you found it helpful, please share and follow for more web development insights!