本节摘要:Next.js 开箱即快,但"快"的上限取决于你怎么用它。本节讲清核心 Web 指标(LCP/CLS/INP)、渲染与缓存的性能选择、图片字体优化、以及性能分析工具——建立"先测量后优化"的正确姿势。
阅读完本节,你应当能够:
Next.js 的性能优劣,80% 在架构决策时已注定:页面用 SSG 还是 SSR?数据要不要缓存?图片优化没开?组件是服务器还是客户端?选对了默认就快,选错了再优化也有限。
直觉类比:性能像"通勤效率"——住得近(SSG/缓存)比"跑得快"(优化算法)重要得多。先选对"住在哪"(渲染策略),再谈"怎么跑"(细节优化)。
💡 关键直觉:Next.js 的默认值就是为性能设计的——静态优先、图片自动优化、字体自托管、客户端组件只发必要 JS。优化的核心工作是"别破坏默认值",其次是"针对瓶颈做定向优化"。
| 指标 | 含义 | 感知 | 目标 |
|---|---|---|---|
| LCP | 最大内容绘制(首屏主体出现) | 加载快慢 | < 2.5s |
| CLS | 累积布局偏移(页面跳动) | 稳定与否 | < 0.1 |
| INP | 交互响应(点击/输入反馈) | 卡不卡 | < 200ms |
Next.js 的对应手段:
// 1. 能静态就静态(默认) export const revalidate = 3600; // ISR:每小时刷新 // 2. 动态页面用 Streaming 拆首屏 // app/blog/page.tsx import { Suspense } from "react"; import { PostList } from "./PostList"; export default function BlogPage() { return ( <div> <h1>博客</h1> <Suspense fallback={<div>加载文章...</div>}> <PostList /> {/* 数据就绪后流入,其余先显示 */} </Suspense> </div> ); }
Streaming 的价值:页面骨架立即显示,慢数据部分异步流入——LCP 显著改善。
// 图片:自动优化 + 首屏 priority <Image src="/hero.png" alt="主视觉" fill priority sizes="100vw" /> // 字体:next/font 自动子集化 import { Inter } from "next/font/google";
next/dynamic 懒加载;import dynamic from "next/dynamic"; const HeavyChart = dynamic(() => import("@/components/HeavyChart"), { ssr: false, // 客户端才加载 loading: () => <div>图表加载中...</div>, });
| 层 | 手段 | 收益 |
|---|---|---|
| 页面层 | SSG/ISR | 构建时生成,CDN 缓存 |
| 数据层 | fetch revalidate | 定时刷新,减少上游请求 |
| 组件层 | React cache() | 同请求内复用结果 |
| 边缘层 | CDN + 中间件 | 全球加速 |
// React cache:同请求内重复调用只执行一次 import { cache } from "react"; export const getCurrentUser = cache(async () => { return await db.user.findUnique({ where: { id: session.userId } }); });
# 本地测量 npm run build # 查看各路由的 JS 体积 npm run start # 生产模式
| 工具 | 用途 |
|---|---|
| Lighthouse(Chrome DevTools) | 综合评分与诊断 |
| Vercel Analytics | 真实用户指标(RUM) |
| Next.js Analytics(Speed Insights) | Web Vitals 监控 |
| Chrome Performance 面板 | 深入分析 JS 执行 |
分析决策树:先看 RUM 指标(哪个页面慢)→ 再 Lighthouse(哪类问题)→ 最后 Performance 面板(哪段代码)——从用户数据到代码定位。
| 误区 | 现象 | 正解 |
|---|---|---|
| 所有页面 SSR | 服务器压力大 | 静态优先,按需动态 |
| 首屏图片没 priority | LCP 慢 | 首屏图加 priority |
| use client 泛滥 | JS 体积大 | 服务器组件优先 |
| 忽略 CLS | 页面跳动 | 图片/字体占位 |
| 凭感觉优化 | 白忙 | 先看指标再动手 |
// 优化前:app/report/page.tsx(全 SSR + 同步加载) export const dynamic = "force-dynamic"; export default async function ReportPage() { const [summary, charts] = await Promise.all([ getSummary(), getCharts(), // 两个慢数据串行等 ]); return <ReportView summary={summary} charts={charts} />; } // 优化后:静态骨架 + Streaming + 图片优化 import { Suspense } from "react"; import { SummaryCard } from "./SummaryCard"; import { ChartsPanel } from "./ChartsPanel"; export default function ReportPage() { return ( <div> <h1>数据报表</h1> <Suspense fallback={<div>汇总加载中...</div>}> <SummaryCard /> {/* 快数据先显示 */} </Suspense> <Suspense fallback={<div>图表加载中...</div>}> <ChartsPanel /> {/* 慢数据后流入 */} </Suspense> </div> ); }
页面骨架秒现,汇总卡先出,图表异步流入——用户感知的加载时间大幅缩短,而代码改动只是把数据拆进 Suspense。

核心原则:先测量(RUM/Lighthouse)再优化;能静态就静态;客户端 JS 最小化。性能优化的每一步都要"改前测、改后测"——数据说话,不凭感觉。
问:怎么确认瓶颈在哪?
先看数据再动手:Vercel Analytics(真实用户指标)→ Lighthouse(本地诊断)→ Chrome Performance(代码级定位)。没有测量就没有优化。
问:SSR 页面慢怎么办?
三个方向:数据是否串行(改 Promise.all 并行);能否拆 Suspense(骨架先出);能否转 ISR(能容忍缓存就用)。SSR 是最后手段,能静态优先静态。
问:客户端 JS 太大怎么办?
检查 use client 是否过度(非交互组件移回服务器);大组件动态导入(next/dynamic);第三方库按需引入。目标是首屏 JS 最小。
问:图片优化要注意什么?
首屏图加 priority(跳过懒加载);指定尺寸防 CLS;用对象存储 + CDN 分发用户上传图。
问:缓存导致数据不新鲜怎么处理?
能接受短暂延迟就用 ISR(revalidate);必须实时用 force-dynamic + 客户端轮询;数据变更时用 revalidatePath 主动刷新。
性能优化的正道是"先测量后优化"——RUM 看用户指标、Lighthouse 看诊断、Performance 看代码。手段按指标对症:LCP 用渲染策略与 Streaming、CLS 用占位、INP 用 JS 最小化。记住 Next.js 默认即快,别破坏默认值。
给练习项目做一次"性能体检":用 Lighthouse 跑一遍本地生产模式,记录 LCP/CLS/INP 与性能评分;再用 npm run build 查看各路由的 JS 体积。找到最慢/最大的页面,对症优化:数据串行改 Promise.all、整页同步改 Suspense 拆分、非交互组件从客户端移回服务器。
优化后重跑 Lighthouse 对比分数——用数据证明优化有效,而不是凭感觉说"变快了"。这一步做完,你就建立了"性能优化 = 测量-定位-优化-复测"的完整闭环。