9.1 next/image 图片优化
Next.js 内置的 Image 组件是对原生 <img> 标签的增强封装,自动处理图片优化的方方面面。使用 next/image 是 Next.js 中图片处理的强制最佳实践。
- 自动格式转换 根据浏览器支持情况,自动将图片转为 WebP(-30%体积)或 AVIF(-50%体积),无需手动处理。
- 响应式尺寸 根据设备视口和像素密度(DPR)请求合适尺寸的图片,避免在小屏加载大图浪费带宽。
-
懒加载
视口外的图片默认不加载(
loading="lazy"),减少初始页面加载时间。 -
CLS 防抖
要求
width和height,在图片加载前预留空间,防止 Cumulative Layout Shift(布局位移),优化 Core Web Vitals 得分。 -
priority 属性
对首屏关键图片(如 Hero 背景图、头像)设置
priority,使其优先加载,改善 LCP 指标。
import Image from 'next/image'
// 本地图片(自动获取尺寸)
import heroImage from '@/public/hero.jpg'
function HeroSection() {
return (
<Image
src={heroImage}
alt="Hero 图片"
priority {/* LCP 关键图片,优先加载 */}
placeholder="blur" {/* 模糊占位符 */}
/>
)
}
// 远程图片(需要在 next.config.ts 添加域名白名单)
function UserAvatar({ url }: { url: string }) {
return (
<Image
src={url}
alt="用户头像"
width={48}
height={48}
sizes="48px"
className="rounded-full"
/>
)
}
// 填充父容器(需要父元素 position: relative)
function CoverImage({ src }: { src: string }) {
return (
<div style={{ position: 'relative', height: '300px' }}>
<Image
src={src}
alt="封面"
fill
sizes="(max-width: 768px) 100vw, 50vw"
style={{ objectFit: 'cover' }}
/>
</div>
)
}
TSX
9.2 next/font 字体优化
字体是影响页面渲染性能的重要因素。next/font 在构建时下载字体文件,将其内联到 CSS 中,消除了对 Google Fonts 等外部服务的运行时请求,避免了字体加载导致的布局位移(FOUT/FOIT)。
// app/layout.tsx — 字体配置
import { Inter, Noto_Sans_SC, Fira_Code } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter', // 暴露为 CSS 变量
display: 'swap', // 字体加载策略
})
const notoSansSC = Noto_Sans_SC({
subsets: ['latin'],
weight: ['400', '600', '700'], // 只加载需要的字重
variable: '--font-noto',
})
const firaCode = Fira_Code({
subsets: ['latin'],
variable: '--font-code',
})
export default function RootLayout({ children }) {
return (
<html lang="zh-CN" className=`${inter.variable} ${notoSansSC.variable} ${firaCode.variable}`>
<body>{children}</body>
</html>
)
}
TSX
/* globals.css — 使用 CSS 变量应用字体 */
body {
font-family: var(--font-noto), var(--font-inter), sans-serif;
}
code, pre {
font-family: var(--font-code), monospace;
}
CSS
9.3 Metadata API — SEO 最佳实践
Next.js 15 提供了基于导出的 Metadata API,替代了传统的 <Head> 组件。支持静态导出 metadata 对象和动态生成的 generateMetadata 函数,自动处理 <title>、<meta>、Open Graph、Twitter Card 等 SEO 标签。
// app/layout.tsx — 根 Metadata(默认值)
import type { Metadata } from 'next'
export const metadata: Metadata = {
// title 模板:子页面会显示 "文章标题 | My Blog"
title: {
template: '%s | My Blog',
default: 'My Blog',
},
description: '一个用 Next.js 构建的博客',
metadataBase: new URL('https://myblog.com'),
openGraph: {
type: 'website',
siteName: 'My Blog',
locale: 'zh_CN',
images: [{ url: '/og-default.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
creator: '@yourhandle',
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true },
},
}
TS
// app/blog/[slug]/page.tsx — 动态 Metadata
import type { Metadata, ResolvingMetadata } from 'next'
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> },
parent: ResolvingMetadata
): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
if (!post) return { title: '文章不存在' }
// 继承父级 openGraph 配置
const parentOG = (await parent).openGraph?.images ?? []
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [
{ url: post.coverImage, width: 1200, height: 630 },
...parentOG,
],
},
alternates: {
canonical: `/blog/${slug}`,
},
}
}
TS
9.4 generateStaticParams — 静态路由预渲染
对于动态路由(如 /blog/[slug]),使用 generateStaticParams 告诉 Next.js 在构建时预渲染哪些参数组合。这相当于 Pages Router 中的 getStaticPaths,但语法更简洁。
预渲染的好处是:这些页面在构建时生成静态 HTML,部署后直接从 CDN 边缘节点提供服务,无需服务器计算,首字节时间(TTFB)极短。
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
// 在构建时获取所有文章 slug
const posts = await db.post.findMany({
where: { published: true },
select: { slug: true },
})
// 返回参数数组,Next.js 会为每个 slug 预渲染一个页面
return posts.map(post => ({ slug: post.slug }))
}
// dynamicParams: false — 未预渲染的路由返回 404
// dynamicParams: true(默认)— 未预渲染的路由按需 SSR
export const dynamicParams = true
TS
9.5 Bundle 分析与优化
Bundle 大小直接影响页面加载性能。Next.js 提供了官方的 Bundle 分析工具,可以可视化每个页面的 JavaScript 依赖构成,找出体积过大的依赖包进行优化。
# 安装分析工具
pnpm add -D @next/bundle-analyzer
# 分析构建(生成可视化报告)
ANALYZE=true pnpm build
SHELL
// next.config.ts
import bundleAnalyzer from '@next/bundle-analyzer'
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
})
export default withBundleAnalyzer({
// 你的 next.config 配置
})
TS
常见 Bundle 优化技巧
// 1. 动态导入(代码分割,按需加载)
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('@/components/chart'), {
loading: () => <p>加载图表中...</p>,
ssr: false, // 图表库通常需要 window,禁用 SSR
})
// 2. 只导入需要的模块(避免全量导入)
// ❌ 导入整个 lodash(~70KB)
import _ from 'lodash'
// ✅ 只导入需要的函数(~2KB)
import debounce from 'lodash/debounce'
// 3. 使用 Tree-shaking 友好的包
// ❌ date-fns 旧版(需要手动按需引入)
// ✅ date-fns v3 / day.js(默认支持 tree-shaking)
import { format } from 'date-fns'
// 4. 服务端依赖放在 Server Component(不进入 Bundle)
// marked、gray-matter、prisma 等只在 Server Component 使用,
// 它们的代码永远不会出现在客户端 Bundle 中
TSX
9.6 Core Web Vitals 优化总结
Google 的 Core Web Vitals(核心网页指标)是衡量用户体验的关键指标,直接影响 SEO 排名。
| 指标 | 全称 | 目标值 | Next.js 优化手段 |
|---|---|---|---|
| LCP | 最大内容绘制 | <2.5s | Image priority、字体预加载、SSR/SSG |
| INP | 交互到下一帧绘制 | <200ms | useTransition、Server Actions、减少 JS 执行 |
| CLS | 累计布局位移 | <0.1 | Image width/height、字体 display:swap、骨架屏 |
| FCP | 首次内容绘制 | <1.8s | 流式渲染、边缘部署、HTTP/2 Push |
使用 Vercel Speed Insights在 Vercel 部署后,在 layout.tsx 中添加 <SpeedInsights />(来自 @vercel/speed-insights/next),即可实时监控真实用户的 Core Web Vitals 数据,指导针对性优化。