Next.js 字体优化 ⚡
🎯 概述
Next.js 提供了内置的字体优化功能,通过 next/font 模块自动优化字体加载,消除外部网络请求,提升性能和用户体验。
📦 为什么需要字体优化?
传统字体加载的问题
- 布局偏移(CLS):字体加载时导致页面布局跳动
- 性能影响:外部字体请求增加加载时间
- 隐私问题:使用 Google Fonts 等外部服务可能泄露用户信息
- 网络依赖:依赖第三方 CDN 的可用性
Next.js 字体优化的优势
- ✅ 自动自托管字体文件
- ✅ 零布局偏移
- ✅ 自动字体子集化
- ✅ 预加载关键字体
- ✅ 支持 Google Fonts 和本地字体
🚀 使用 Google Fonts
基本用法
tsx
// 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="zh" className={inter.className}>
<body>{children}</body>
</html>
)
}多字重配置
tsx
import { Roboto } from 'next/font/google'
const roboto = Roboto({
weight: ['400', '700', '900'],
subsets: ['latin'],
display: 'swap',
})可变字体
tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
export default function RootLayout({ children }) {
return (
<html lang="zh" className={inter.variable}>
<body>{children}</body>
</html>
)
}在 CSS 中使用:
css
/* globals.css */
body {
font-family: var(--font-inter);
}📝 使用本地字体
加载本地字体文件
tsx
// app/layout.tsx
import localFont from 'next/font/local'
const myFont = localFont({
src: './fonts/my-font.woff2',
display: 'swap',
})
export default function RootLayout({ children }) {
return (
<html lang="zh" className={myFont.className}>
<body>{children}</body>
</html>
)
}多文件配置
tsx
const myFont = localFont({
src: [
{
path: './fonts/my-font-regular.woff2',
weight: '400',
style: 'normal',
},
{
path: './fonts/my-font-bold.woff2',
weight: '700',
style: 'normal',
},
{
path: './fonts/my-font-italic.woff2',
weight: '400',
style: 'italic',
},
],
variable: '--font-my-font',
})🎨 多字体组合
组合使用多种字体
tsx
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
display: 'swap',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono',
display: 'swap',
})
export default function RootLayout({ children }) {
return (
<html lang="zh" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}在 Tailwind CSS 中使用
js
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
mono: ['var(--font-roboto-mono)'],
},
},
},
}使用:
tsx
<h1 className="font-sans">标题文本</h1>
<code className="font-mono">代码文本</code>⚙️ 字体配置选项
完整配置示例
tsx
import { Inter } from 'next/font/google'
const inter = Inter({
// 字符子集
subsets: ['latin', 'latin-ext'],
// 字重
weight: ['400', '500', '600', '700'],
// 字体样式
style: ['normal', 'italic'],
// 显示策略
display: 'swap',
// 预加载
preload: true,
// 回退字体
fallback: ['system-ui', 'arial'],
// 调整大小
adjustFontFallback: true,
// CSS 变量名
variable: '--font-inter',
})display 选项说明
auto:浏览器默认行为block:短暂隐藏文本,然后显示字体swap:立即显示回退字体,加载后切换(推荐)fallback:极短时间隐藏,然后显示回退字体optional:极短时间隐藏,可能不切换字体
🎯 特定页面使用字体
在单个页面中使用
tsx
// app/blog/page.tsx
import { Merriweather } from 'next/font/google'
const merriweather = Merriweather({
weight: '400',
subsets: ['latin'],
})
export default function BlogPage() {
return (
<div className={merriweather.className}>
<h1>博客文章</h1>
<p>这里使用特殊的字体...</p>
</div>
)
}在组件中使用
tsx
// components/Heading.tsx
import { Playfair_Display } from 'next/font/google'
const playfair = Playfair_Display({
subsets: ['latin'],
weight: '700',
})
export function Heading({ children }) {
return (
<h1 className={playfair.className}>
{children}
</h1>
)
}📊 性能优化技巧
1. 使用字体子集
tsx
const inter = Inter({
subsets: ['latin'], // 只加载拉丁字符
// 对于中文网站
// subsets: ['latin', 'chinese-simplified']
})2. 限制字重数量
tsx
// ❌ 不推荐:加载所有字重
const roboto = Roboto({
weight: ['100', '300', '400', '500', '700', '900'],
})
// ✅ 推荐:只加载需要的字重
const roboto = Roboto({
weight: ['400', '700'],
})3. 使用可变字体
tsx
// 可变字体包含所有字重,但文件大小更小
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})4. 预加载关键字体
tsx
const inter = Inter({
subsets: ['latin'],
preload: true, // 预加载字体
display: 'swap', // 快速显示内容
})🌐 中文字体优化
使用本地中文字体
tsx
import localFont from 'next/font/local'
const notoSansSC = localFont({
src: [
{
path: './fonts/NotoSansSC-Regular.woff2',
weight: '400',
},
{
path: './fonts/NotoSansSC-Bold.woff2',
weight: '700',
},
],
variable: '--font-noto-sans-sc',
display: 'swap',
})字体子集化
由于中文字体文件较大,建议使用工具进行子集化:
bash
# 使用 fonttools 进行子集化
pip install fonttools brotli
# 提取常用汉字
pyftsubset font.ttf \
--output-file=font-subset.woff2 \
--flavor=woff2 \
--text-file=common-chars.txt🔍 调试和测试
检查字体加载
在浏览器开发者工具中:
- Network 标签:查看字体文件加载情况
- Performance 标签:检查 CLS 指标
- Lighthouse:运行性能审计
验证字体应用
tsx
// 添加 data 属性用于调试
<div className={inter.className} data-font="inter">
内容
</div>📚 最佳实践
1. 全局字体配置
tsx
// app/layout.tsx
import { Inter, Noto_Sans_SC } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
display: 'swap',
})
const notoSansSC = Noto_Sans_SC({
subsets: ['chinese-simplified'],
weight: ['400', '500', '700'],
variable: '--font-noto-sans-sc',
display: 'swap',
})
export default function RootLayout({ children }) {
return (
<html
lang="zh"
className={`${inter.variable} ${notoSansSC.variable}`}
>
<body>{children}</body>
</html>
)
}2. CSS 配置
css
/* app/globals.css */
:root {
--font-inter: var(--font-inter);
--font-noto-sans-sc: var(--font-noto-sans-sc);
}
body {
font-family: var(--font-noto-sans-sc), var(--font-inter), system-ui, sans-serif;
}
code {
font-family: 'Courier New', monospace;
}3. 条件加载
tsx
// 根据语言加载不同字体
import { Inter, Noto_Sans_SC } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
const notoSansSC = Noto_Sans_SC({
subsets: ['chinese-simplified'],
weight: ['400', '700']
})
export default function RootLayout({
children,
params: { lang }
}) {
const font = lang === 'zh' ? notoSansSC : inter
return (
<html lang={lang} className={font.className}>
<body>{children}</body>
</html>
)
}🎓 实战示例
完整的字体配置
tsx
// app/fonts.ts
import { Inter, Roboto_Mono, Playfair_Display } from 'next/font/google'
import localFont from 'next/font/local'
// 正文字体
export const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
display: 'swap',
})
// 代码字体
export const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono',
display: 'swap',
})
// 标题字体
export const playfair = Playfair_Display({
subsets: ['latin'],
variable: '--font-playfair',
display: 'swap',
})
// 自定义中文字体
export const customChinese = localFont({
src: './fonts/custom-chinese.woff2',
variable: '--font-custom-chinese',
display: 'swap',
})tsx
// app/layout.tsx
import { inter, robotoMono, playfair, customChinese } from './fonts'
export default function RootLayout({ children }) {
return (
<html
lang="zh"
className={`
${inter.variable}
${robotoMono.variable}
${playfair.variable}
${customChinese.variable}
`}
>
<body>{children}</body>
</html>
)
}css
/* app/globals.css */
body {
font-family: var(--font-custom-chinese), var(--font-inter), sans-serif;
}
h1, h2, h3 {
font-family: var(--font-playfair), serif;
}
code, pre {
font-family: var(--font-roboto-mono), monospace;
}🔗 相关资源
下一步:学习 Next.js 元数据管理,了解如何优化 SEO 和社交媒体分享。