Skip to content

Next.js 字体优化 ⚡

🎯 概述

Next.js 提供了内置的字体优化功能,通过 next/font 模块自动优化字体加载,消除外部网络请求,提升性能和用户体验。

📦 为什么需要字体优化?

传统字体加载的问题

  1. 布局偏移(CLS):字体加载时导致页面布局跳动
  2. 性能影响:外部字体请求增加加载时间
  3. 隐私问题:使用 Google Fonts 等外部服务可能泄露用户信息
  4. 网络依赖:依赖第三方 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

🔍 调试和测试

检查字体加载

在浏览器开发者工具中:

  1. Network 标签:查看字体文件加载情况
  2. Performance 标签:检查 CLS 指标
  3. 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 和社交媒体分享。