Next.js 字体优化:使用 `next/font` 告别布局偏移和性能瓶颈
Next.js 字体优化:使用 next/font
告别布局偏移和性能瓶颈
作者:码力无边
在上一篇文章中,我们利用 next/image
组件驯服了图片这头性能猛兽。然而,在追求极致用户体验的道路上,还有一个潜藏的“性能刺客”——网页字体 (Web Fonts)。
自定义字体,如谷歌字体 (Google Fonts) 或 Adobe Fonts,能够极大地提升网站的设计感和品牌辨识度。但它们的加载方式也常常会引发两个棘手的问题:
-
布局偏移 (Cumulative Layout Shift, CLS):浏览器在渲染页面时,如果自定义字体文件尚未下载完成,它会先使用一个后备的系统字体(如 Arial 或 Times New Roman)来显示文本。当自定义字体最终加载完毕后,文本会突然“变脸”,由于两种字体的字形、间距不同,会导致段落宽度、行高发生变化,从而引发整个页面布局的“抖动”或“偏移”。这对用户体验是毁灭性的,也是谷歌 Core Web Vitals 重点关注的负面指标。
-
性能瓶颈 (Flash of Unstyled Text, FOUT):在字体加载完成前,用户可能会看到短暂的无样式文本(或在某些配置下是不可见文本),这种闪烁感同样会降低网站的专业度和体验。此外,从外部服务器(如
fonts.googleapis.com
)下载字体文件会增加额外的网络请求,拖慢页面渲染速度。
传统的解决方案通常需要复杂的 CSS @font-face
配置、font-display: swap;
属性以及 preload
链接,手动操作既繁琐又容易出错。
为了彻底解决这些问题,Next.js 13 引入了一个全新的、内置的字体优化系统:next/font
。这个模块让你能够以一种极其简单且高效的方式使用自定义字体,同时自动为你处理所有底层优化。
next/font
的核心优势
next/font
模块通过一系列智能的自动化操作,从根本上解决了传统字体加载的痛点:
- 零布局偏移:
next/font
在构建时计算出自定义字体的尺寸调整指标,并在 CSS 中自动生成size-adjust
属性。这使得后备字体的尺寸能够被调整到与最终加载的自定义字体几乎完全一致,从而彻底消除字体加载导致的布局偏移。 - 自托管与性能:当你使用 Google Fonts 时,
next/font
不会向 Google 的服务器发送任何请求。相反,它会在构建时自动下载指定的字体文件,并将其与其他静态资源一起托管在你的服务器上。这意味着:- 没有额外的网络请求:减少了 DNS 查询和 TCP 连接的开销。
- 提升隐私:用户的浏览器不会与第三方字体服务商通信。
- 自动
font-display: swap;
:默认情况下,它会应用最佳实践,确保在字体加载期间文本始终可见。 - 易于使用:提供了简洁的函数式 API,无论是使用 Google Fonts 还是本地字体文件,都异常方便。
实战一:使用 Google Fonts
这是最常见也最简单的用法。next/font/google
模块内置了对所有 Google 字体的支持。
1. 导入字体函数
假设我们想在整个应用中使用 Inter
字体作为主字体,Roboto Mono
作为代码字体。
pages/_app.tsx
(或者你的布局文件中)
import { Inter, Roboto_Mono } from 'next/font/google';
import type { AppProps } from 'next/app';
import '../styles/globals.css';// 2. 调用字体函数并进行配置
const inter = Inter({subsets: ['latin'], // 指定需要的字符子集variable: '--font-inter', // 创建一个 CSS 变量display: 'swap',
});const roboto_mono = Roboto_Mono({subsets: ['latin'],variable: '--font-roboto-mono',display: 'swap',
});// 3. 将字体应用到应用中
export default function MyApp({ Component, pageProps }: AppProps) {// 使用模板字符串组合多个字体的 classNamereturn (<main className={`${inter.variable} ${roboto_mono.variable}`}><Component {...pageProps} /></main>);
}
4. 在你的全局 CSS 中使用 CSS 变量
现在,你可以在 styles/globals.css
中使用我们创建的 CSS 变量来定义全局字体。
styles/globals.css
html,
body {/* 默认使用 Inter 字体 */font-family: var(--font-inter);
}/* 对于 pre 和 code 标签,使用等宽字体 */
pre,
code {font-family: var(--font-roboto-mono);
}
代码解读:
- 我们从
next/font/google
导入了对应的字体函数。 subsets
: 这是一个必填项,它告诉 Next.js 只下载你需要的语言字符集(如latin
用于英语,cyrillic
用于俄语等),从而减小字体文件体积。variable
: 这是一个非常强大的功能。它不会直接将className
应用到元素上,而是创建一个 CSS 变量(例如--font-inter
),让你可以在 CSS 中更灵活地使用这个字体。这是推荐的最佳实践。- 我们将生成的
variable
类名应用到了顶层的main
元素上,使其在整个应用中生效。
实战二:使用本地字体文件
如果你的字体文件(如 .ttf
, .woff2
)是存放在项目本地的,可以使用 next/font/local
。
文件结构:
my-app/
├── fonts/
│ └── MyCustomFont-Regular.woff2
├── pages/
└── ...
使用 localFont
函数
import localFont from 'next/font/local';
import Link from 'next/link';// 1. 加载本地字体
const myFont = localFont({src: '../fonts/MyCustomFont-Regular.woff2',display: 'swap',
});// 2. 将字体应用到特定组件
// 注意 .className 的用法
export default function MyComponent() {return (<div><h1 className={myFont.className}>这段文字使用了我的自定义本地字体</h1><p>这段文字使用默认字体。</p><Link href="/"><a className={myFont.className}>这个链接也使用了自定义字体</a></Link></div>);
}
代码解读:
src
属性指向你的本地字体文件路径。- 对于单个组件或页面的局部字体应用,直接使用
myFont.className
是最简单的方式。它会返回一个自动生成的类名,你只需将其应用到需要该字体的元素上即可。
总结:为什么你应该立即采用 next/font
next/font
是 Next.js 对前端性能优化理念的又一次完美体现。它将一个复杂、易错、且对用户体验影响巨大的问题,抽象成了一个简单、优雅且几乎零配置的解决方案。
核心优势回顾:
- 自动优化:自托管、预加载、
size-adjust
等所有最佳实践都已内置。 - 性能提升:消除外部网络请求,减小字体文件体积。
- 体验改善:彻底告别布局偏移 (CLS) 和字体加载闪烁 (FOUT)。
- 开发便捷:无论是 Google Fonts 还是本地字体,API 都极其简洁。
在你的 Next.js 项目中,已经没有任何理由再使用传统的 <link>
标签或 @import
方式来引入字体了。next/font
应该是你处理所有字体需求的首选,也是唯一的选择。
我们已经优化了图片和字体,这是构成页面视觉的两大核心元素。在下一篇文章中,我们将把目光投向另一个常见的性能瓶颈——第三方脚本(如分析工具、广告脚本、客服插件等),学习如何使用 next/script
来驯服它们。敬请期待!