字体优化
深入理解 Next.js 字体优化机制,包括 Google Fonts、本地字体、CSS 变量、Tailwind CSS 集成和预加载
概述
next/font 模块自动优化你的字体并移除外部网络请求,以提高隐私和性能。
主要特性:
- 内置自托管:自动托管任何字体文件
- 零布局偏移:优化加载 Web 字体,无布局偏移
- 隐私保护:无需向 Google 发送请求
- 性能优化:字体在构建时下载并与静态资源一起托管
基本使用
从 next/font/google 或 next/font/local 导入字体,调用函数并设置 className:
import { Geist } from 'next/font/google'
const geist = Geist({
subsets: ['latin'],
})
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={geist.className}>
<body>{children}</body>
</html>
)
}字体作用域限定在使用它们的组件中。要将字体应用于整个应用程序,将其添加到根布局。
Google Fonts
可以自动自托管任何 Google 字体。字体作为静态资源存储并从与部署相同的域提供服务。
基本用法
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>
)
}指定字重
对于非可变字体,需要指定 weight:
import { Roboto } from 'next/font/google'
const roboto = Roboto({
weight: '400',
subsets: ['latin'],
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={roboto.className}>
<body>{children}</body>
</html>
)
}多个字重
import { Roboto } from 'next/font/google'
const roboto = Roboto({
weight: ['400', '700'],
subsets: ['latin'],
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={roboto.className}>
<body>{children}</body>
</html>
)
}可变字体
对于可变字体,不需要指定字重:
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>
)
}指定子集
import { Noto_Sans_SC } from 'next/font/google'
const notoSansSC = Noto_Sans_SC({
weight: ['400', '700'],
subsets: ['latin', 'chinese-simplified'],
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN" className={notoSansSC.className}>
<body>{children}</body>
</html>
)
}本地字体
要使用本地字体,从 next/font/local 导入并指定字体文件的 src。
单个字体文件
import localFont from 'next/font/local'
const myFont = localFont({
src: './my-font.woff2',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={myFont.className}>
<body>{children}</body>
</html>
)
}多个字体文件
如果要为单个字体系列使用多个文件,src 可以是数组:
import localFont from 'next/font/local'
const roboto = localFont({
src: [
{
path: './Roboto-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: './Roboto-Italic.woff2',
weight: '400',
style: 'italic',
},
{
path: './Roboto-Bold.woff2',
weight: '700',
style: 'normal',
},
{
path: './Roboto-BoldItalic.woff2',
weight: '700',
style: 'italic',
},
],
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={roboto.className}>
<body>{children}</body>
</html>
)
}可变本地字体
import localFont from 'next/font/local'
const myFont = localFont({
src: './my-font-variable.woff2',
variable: '--font-my',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={myFont.variable}>
<body>{children}</body>
</html>
)
}配置选项
subsets
字体子集数组,用于预加载。
const inter = Inter({
subsets: ['latin', 'latin-ext'],
})weight
字体粗细,可以是字符串或字符串数组。
// 单个字重
const roboto = Roboto({
weight: '400',
subsets: ['latin'],
})
// 多个字重
const roboto = Roboto({
weight: ['400', '700'],
subsets: ['latin'],
})style
字体样式,可以是字符串或字符串数组。
const roboto = Roboto({
weight: '400',
style: ['normal', 'italic'],
subsets: ['latin'],
})display
字体显示策略。
const inter = Inter({
subsets: ['latin'],
display: 'swap', // 'auto' | 'block' | 'swap' | 'fallback' | 'optional'
})preload
布尔值,指定是否应预加载字体。默认为 true。
const inter = Inter({
subsets: ['latin'],
preload: true,
})variable
CSS 变量名称,用于与 CSS 变量一起使用。
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})adjustFontFallback
布尔值,是否自动使用后备字体来减少累积布局偏移。
const inter = Inter({
subsets: ['latin'],
adjustFontFallback: true, // 默认为 true
})fallback
字体无法加载时使用的后备字体数组。
const inter = Inter({
subsets: ['latin'],
fallback: ['system-ui', 'arial'],
})应用字体
使用 className
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function Page() {
return <h1 className={inter.className}>Hello, Next.js!</h1>
}使用 style
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function Page() {
return <h1 style={inter.style}>Hello, Next.js!</h1>
}使用 CSS 变量
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
)
}body {
font-family: var(--font-inter);
}多个字体
在同一文件中使用多个字体
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,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}body {
font-family: var(--font-inter);
}
code {
font-family: var(--font-roboto-mono);
}在不同组件中使用不同字体
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function Page() {
return <h1 className={inter.className}>Home Page</h1>
}import { Roboto } from 'next/font/google'
const roboto = Roboto({ weight: '400', subsets: ['latin'] })
export default function About() {
return <h1 className={roboto.className}>About Page</h1>
}与 Tailwind CSS 集成
配置 Tailwind
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,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
mono: ['var(--font-roboto-mono)'],
},
},
},
plugins: [],
}使用 Tailwind 类
export default function Page() {
return (
<div>
<h1 className="font-sans">Hello with Inter</h1>
<code className="font-mono">console.log('Hello')</code>
</div>
)
}预加载
字体函数在网站页面上调用时,不会在所有路由上全局可用和预加载。相反,字体仅根据使用它的文件类型在相关路由上预加载:
- 如果是唯一页面,则在该页面的唯一路由上预加载
- 如果是布局,则在布局包装的所有路由上预加载
- 如果是根布局,则在所有路由上预加载
示例:根布局预加载
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
)
}在此示例中,Inter 字体将在所有路由上预加载。
示例:页面级预加载
import { Roboto } from 'next/font/google'
const roboto = Roboto({ weight: '400', subsets: ['latin'] })
export default function About() {
return <h1 className={roboto.className}>About Page</h1>
}在此示例中,Roboto 字体仅在 /about 路由上预加载。
重用字体定义
创建字体文件
import { Inter, Lora, Source_Code_Pro } from 'next/font/google'
import localFont from 'next/font/local'
export const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
export const lora = Lora({
subsets: ['latin'],
variable: '--font-lora',
})
export const sourceCodePro = Source_Code_Pro({
subsets: ['latin'],
variable: '--font-source-code-pro',
})
export const myLocalFont = localFont({
src: './my-font.woff2',
variable: '--font-my-local',
})在多个文件中使用
import { inter } from '@/styles/fonts'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
)
}import { lora, sourceCodePro } from '@/styles/fonts'
export default function Page() {
return (
<div>
<h1 className={lora.className}>Title in Lora</h1>
<code className={sourceCodePro.className}>Code in Source Code Pro</code>
</div>
)
}配置路径别名
{
"compilerOptions": {
"paths": {
"@/fonts": ["./styles/fonts"]
}
}
}import { inter, lora } from '@/fonts'
export default function About() {
return (
<div>
<h1 className={inter.className}>About</h1>
<p className={lora.className}>Description</p>
</div>
)
}高级用法
条件字体加载
import { Inter, Noto_Sans_SC } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
const notoSansSC = Noto_Sans_SC({
weight: ['400', '700'],
subsets: ['chinese-simplified'],
variable: '--font-noto-sans-sc',
})
export default function RootLayout({
children,
params,
}: {
children: React.ReactNode
params: { locale: string }
}) {
const fontClass = params.locale === 'zh'
? notoSansSC.variable
: inter.variable
return (
<html lang={params.locale} className={fontClass}>
<body>{children}</body>
</html>
)
}字体回退
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
fallback: ['system-ui', 'arial'],
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
)
}禁用预加载
import { Roboto } from 'next/font/google'
const roboto = Roboto({
weight: '400',
subsets: ['latin'],
preload: false, // 禁用预加载
})
export default function Page() {
return <h1 className={roboto.className}>Page Title</h1>
}最佳实践
1. 在根布局中定义全局字体
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
)
}2. 使用 CSS 变量以获得更好的灵活性
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,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}3. 仅加载所需的子集
const inter = Inter({
subsets: ['latin'], // 仅加载 latin 子集
})4. 为可变字体省略字重
// 好的做法 - 可变字体
const inter = Inter({
subsets: ['latin'],
})
// 不好的做法 - 可变字体不需要指定字重
const inter = Inter({
weight: '400',
subsets: ['latin'],
})5. 重用字体定义
export const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})6. 使用 display: 'swap' 改善性能
const inter = Inter({
subsets: ['latin'],
display: 'swap', // 使用后备字体直到自定义字体加载完成
})常见问题
字体未加载
确保字体名称正确,并且已指定所需的子集:
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'], // 必须指定子集
})布局偏移
使用 adjustFontFallback 选项:
const inter = Inter({
subsets: ['latin'],
adjustFontFallback: true, // 默认为 true
})本地字体路径错误
确保路径相对于定义字体的文件:
// 如果字体文件在 app/fonts/my-font.woff2
// 并且在 app/layout.tsx 中定义
const myFont = localFont({
src: './fonts/my-font.woff2', // 相对于 app/layout.tsx
})多个字重不工作
对于非可变字体,使用数组指定多个字重:
const roboto = Roboto({
weight: ['400', '700'], // 数组形式
subsets: ['latin'],
})总结
Next.js Font Optimization 提供了强大的字体优化功能:
- 自动优化:自动优化字体并移除外部网络请求
- 自托管:Google Fonts 自动自托管,无需向 Google 发送请求
- 零布局偏移:优化加载 Web 字体,防止布局偏移
- 灵活配置:支持 Google Fonts 和本地字体
- CSS 变量:与 CSS 变量和 Tailwind CSS 无缝集成
- 智能预加载:根据使用位置智能预加载字体
- 性能优化:字体在构建时下载并与静态资源一起托管
通过合理使用 next/font 模块,可以显著提升应用的性能、隐私和用户体验。
上次更新于