Next.js 性能怎么优化?Server Components、图片和缓存策略实战
Next.js 性能优化从三个方向入手:减少客户端 JavaScript 体积、加快页面加载速度、优化数据获取策略。App Router 的 Server Components 天然比 Pages Router 快——大部分代码不发给浏览器。
1. Server Components 优先
App Router 默认所有组件都是 Server Component。只在需要交互(useState、useEffect、onClick)时才加 'use client':
tsx// Server Component(默认)— 不发 JS 给浏览器 export default function Page() { return ( <div> <h1>标题</h1> {/* 静态内容 → Server Component */} <SearchBox /> {/* 需要交互 → Client Component */} <ArticleList articles={articles} /> {/* 纯展示 → Server Component */} </div> ); }
常见错误:整个页面都标 'use client'。正确做法是把交互部分抽成小的 Client Component,外层保持 Server Component。
2. 图片优化
next/image 自动做三件事:懒加载、按设备尺寸返回合适分辨率、WebP 格式转换。
tsximport Image from 'next/image'; <Image src="/hero.jpg" width={1200} height={600} alt="描述" priority // 首屏图片加这个,跳过懒加载 placeholder="blur" // 模糊占位,加载时不会闪白 />
必须填 width 和 height——防止布局偏移(CLS)。priority 只给首屏可见图片用,多了反而拖慢 LCP。
外部图片需要配置域名白名单:
typescript// next.config.js images: { remotePatterns: [ { protocol: 'https', hostname: 'cdn.example.com' }, ], },
3. 字体优化
next/font 自动内联字体文件,消除 FOUT(字体闪烁):
tsximport { Inter } from 'next/font/google'; const inter = Inter({ subsets: ['latin'] }); export default function Layout({ children }) { return <body className={inter.className}>{children}</body>; }
不要用 CSS @import 加载 Google Fonts——它会阻塞渲染。next/font 在构建时下载字体文件,零网络请求。
4. 动态导入减少首屏 JS
非首屏需要的组件用 dynamic 懒加载:
tsximport dynamic from 'next/dynamic'; const HeavyChart = dynamic(() => import('./HeavyChart'), { loading: () => <div>加载中...</div>, ssr: false, // 纯客户端组件不需要 SSR });
ssr: false 跳过服务端渲染——适合图表、编辑器这类重交互、不需要 SEO 的组件。
5. 数据获取策略
App Router 的 fetch 默认有缓存:
tsx// 缓存(默认)— 适合不常变的数据 const data = await fetch('https://api.example.com/data'); // 不缓存 — 每次请求都重新获取 const data = await fetch('https://api.example.com/data', { cache: 'no-store' }); // 定时重新验证 — 适合有一定延迟容忍的数据 const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600 } // 1 小时后重新验证 });
ISR(Incremental Static Regeneration)是 Next.js 的杀手锏:静态页面生成后定时更新,不需要每次请求都渲染。revalidate 的时间根据数据变化频率设置——新闻列表 60 秒,配置数据 3600 秒。
6. Layout 防止重复渲染
App Router 的 layout.tsx 在导航时不会重新渲染。把不会变的 UI(导航栏、页脚)放在 layout 里:
tsx// app/layout.tsx — 只渲染一次 export default function RootLayout({ children }) { return ( <html> <body> <nav>导航栏</nav> {children} {/* 只有这部分会随路由变化 */} <footer>页脚</footer> </body> </html> ); }
7. 分析打包体积
bashnpx @next/bundle-analyzer
生成可视化报告,找出最大的包。常见问题:整个 lodash 被 import(改用 lodash-es 的按需导入)、moment.js 太大(改用 dayjs)、客户端不必要的包。
性能优化检查清单
- Server Component 优先,'use client' 只在需要交互时用
- 图片用 next/image + width/height + priority(首屏)
- 字体用 next/font,不用 CSS @import
- 非首屏组件 dynamic 导入
- fetch 设置合理的 cache/revalidate 策略
- 静态 UI 放 layout,不放 page
- bundle-analyzer 检查大包