关于 nextjs-performance
面向 Next.js 应用开发者的性能优化指南,涵盖 Core Web Vitals、组件模式、缓存和精简打包体积,也包含 Next.js 16 与 React 19 的实践。
主要功能
- 01Core Web Vitals 优化
- 02图像与字体优化
- 03缓存与重新验证
- 04Server Component 模式
- 05使用 Suspense 流式加载
- 06打包分析与精简
适用场景
- 01改善 LCP、INP 或 CLS
- 02减少客户端 JavaScript
- 03加快重复数据加载
- 04缩减初始打包体积
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill nextjs-performance安装后在 Claude Code 中输入/nextjs-performance确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api-routes.mdbundle-optimization.mdcaching-strategies.mdcore-web-vitals.mdfont-optimization.mdimage-optimization.mdmetadata-seo.mdnextjs-16-patterns.mdserver-components.mdstreaming-suspense.md详细介绍
Next.js 性能优化
为优化 Next.js 应用提供专家指导,重点关注 Core Web Vitals、现代模式和最佳实践。
概述
此技能为优化 Next.js 应用提供全面指导,涵盖 Core Web Vitals 优化(LCP、INP、CLS)、现代 React 模式、Server Components、缓存策略和包优化技术。适用于已熟悉 React/Next.js,并希望实施生产级优化的开发者。
适用场景
在处理 Next.js 应用且需要进行以下操作时,使用此技能:
- 优化 Core Web Vitals(LCP、INP、CLS),提升性能和 SEO
- 使用
next/image实现图像优化,加快加载速度 - 使用
next/font配置字体优化,消除布局偏移 - 使用
unstable_cache、revalidateTag或 ISR 设置缓存策略 - 将 Client Components 转换为 Server Components,以缩小包体积
- 实现 Suspense 流式传输,实现渐进式页面加载
- 使用代码拆分和动态导入分析并减小包体积
- 配置元数据和 SEO,提升搜索引擎可见性
- 优化 API 路由处理程序,提升性能
- 应用 Next.js 16 和 React 19 的现代模式
涵盖领域
- Core Web Vitals 优化(LCP、INP、CLS)
- 使用
next/image进行图像优化 - 使用
next/font进行字体优化 - 缓存策略(
unstable_cache、revalidateTag、ISR) - Server Components 模式及从客户端到服务端的转换
- 流式传输和 Suspense,实现渐进式加载
- 包优化和代码拆分
- 元数据和 SEO 配置
- 路由处理程序优化
- Next.js 16 + React 19 模式
使用说明
开始之前
- 使用 Lighthouse 分析当前性能
- 找出瓶颈——在 Chrome DevTools 或 PageSpeed Insights 中检查 Core Web Vitals
- 确定优化优先级:
- LCP 问题 → 重点优化图像和字体
- INP 问题 → 减少 JavaScript,使用 Server Components
- CLS 问题 → 添加尺寸,使用 next/font
如何使用此技能
-
根据要优化的领域,加载相关参考文件:
- 图像问题 →
references/image-optimization.md - 字体/布局偏移 →
references/font-optimization.md - 缓存 →
references/caching-strategies.md - 组件架构 →
references/server-components.md
- 图像问题 →
-
按照常见优化的快速模式操作
-
应用改进前后的转换来优化现有代码
-
更改后使用 Lighthouse 验证改进效果
核心原则
- 优先使用 Server Components——仅在必要时使用 'use client'(浏览器 API、交互功能)
- 尽可能降低组件加载层级——将 Client Components 保留在叶节点
- 使用 Suspense 边界——启用流式传输和渐进式加载
- 合理缓存——使用标签进行精细化重新验证
- 测量改进前后效果——始终使用实际指标验证改进
示例
示例 1:将 Client Component 转换为 Server Component
之前(带有 useEffect 的 Client Component):
'use client'
import { useEffect, useState } from 'react'
export default function ProductList() {
const [products, setProducts] = useState([])
useEffect(() => {
fetch('/api/products').then(r => r.json()).then(setProducts)
}, [])
return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}
之后(直接访问数据的 Server Component):
import { db } from '@/lib/db'
export default async function ProductList() {
const products = await db.product.findMany()
return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}
示例 2:优化图像以改善 LCP
import Image from 'next/image'
export function Hero() {
return (
<div className="relative w-full h-[600px]">
<Image
src="/hero.jpg"
alt="Hero"
fill
priority // Disable lazy loading for LCP
sizes="100vw"
className="object-cover"
/>
</div>
)
}
示例 3:实施缓存策略
import { unstable_cache, revalidateTag } from 'next/cache'
// Cached data function
const getProducts = unstable_cache(
async () => db.product.findMany(),
['products'],
{ revalidate: 3600, tags: ['products'] }
)
// Revalidate on mutation
export async function createProduct(data: FormData) {
'use server'
await db.product.create({ data })
revalidateTag('products')
}
示例 4:设置优化字体
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.variable}>
<body className={`${inter.className} antialiased`}>
{children}
</body>
</html>
)
}
示例 5:实现 Suspense 流式传输
import { Suspense } from 'react'
export default function Page() {
return (
<>
<header>Static content (immediate)</header>
<Suspense fallback={<ProductSkeleton />}>
<ProductList /> {/* Streamed when ready */}
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews /> {/* Independent streaming */}
</Suspense>
</>
)
}
参考文档
针对特定领域的工作,请加载以下参考文档:
| 主题 | 参考文件 |
|---|---|
| Core Web Vitals | references/core-web-vitals.md |
| 图片优化 | references/image-optimization.md |
| 字体优化 | references/font-optimization.md |
| 缓存策略 | references/caching-strategies.md |
| 服务端组件 | references/server-components.md |
| 流式传输/Suspense | references/streaming-suspense.md |
| Bundle 优化 | references/bundle-optimization.md |
| 元数据/SEO | references/metadata-seo.md |
| API 路由 | references/api-routes.md |
| Next.js 16 模式 | references/nextjs-16-patterns.md |
常见转换
| 从 | 转换为 | 优势 |
|---|---|---|
useEffect + fetch | 服务端组件中的直接异步操作 | 减少 70% 的 JS,更快的 TTFB |
使用 useState 管理数据 | 可直接访问数据库的服务端组件 | 代码更简洁,无需水合 |
| 客户端 fetch | unstable_cache 或 ISR | 重复加载更快 |
img 标签 | next/image | 优化的格式、延迟加载 |
| CSS 字体导入 | next/font | CLS 为零,自动优化 |
| 重型组件的静态导入 | dynamic() | 减少初始 Bundle |
最佳实践
图片
- 所有图片都使用
next/image - 仅为 LCP 图片添加
priority - 提供
width和height,或使用带 sizes 的fill - 使用
placeholder="blur"改善用户体验 - 在 next.config.js 中配置 remotePatterns
字体
- 使用
next/font,而不是 CSS 导入 - 指定
subsets以减小体积 - 使用
display: 'swap'立即渲染文本 - 使用
variable选项创建 CSS 变量 - 配置 Tailwind 使用 CSS 变量
缓存
- 使用
unstable_cache缓存开销较大的查询 - 使用有意义的缓存标签进行精细控制
- 为动态内容实施按需重新验证
- 根据数据变化频率设置 TTL
- 使用 revalidatePath 进行路由级失效处理
组件
- 尽可能将客户端组件转换为服务端组件
- 将客户端组件放在组件树的叶子层级
- 使用 Suspense 边界实现渐进式加载
- 实施适当的加载状态
- 对首屏以下的重型组件使用 dynamic()
Bundle
- 使用
dynamic()延迟加载重型组件 - 使用具名导出以改善 tree shaking
- 定期使用
@next/bundle-analyzer 分析 Bundle - 优先使用 ESM 包,而不是 CommonJS
- 对大型库使用 modularizeImports
限制和警告
服务端组件的限制
- 不能使用浏览器 API(window、localStorage、document)
- 不能使用 React Hook(useState、useEffect、useContext)
- 不能使用事件处理程序(onClick、onSubmit)
- 不能使用带有 ssr: false 的动态导入
图片优化限制
priority只能用于首屏图片- 外部图片需要在 next.config.js 中配置
- 除非使用
fill,否则必须提供width和height - 默认不会优化动态 GIF
缓存注意事项
- 必须手动使缓存标签失效
- 开发环境中的数据缓存按请求隔离
- Edge 运行时的缓存行为有所不同
- 缓存用户专属数据时要谨慎
Bundle 大小警告
- 如果关键内容使用动态导入,可能会影响 SEO
- Tree shaking 需要正确使用 ES 模块
- 某些库无法进行 tree shaking(避免使用 barrel 导出)
- 客户端组件会增大 Bundle 体积,请谨慎使用
Next.js 16 + React 19 特定内容
异步参数
// Next.js 15+ params is a Promise
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const post = await fetchPost(slug)
return <article>{post.content}</article>
}
用于 Promise 的 use() Hook
'use client'
import { use, Suspense } from 'react'
function Comments({ promise }: { promise: Promise<Comment[]> }) {
const comments = use(promise) // Suspend until resolved
return <ul>{comments.map(c => <li key={c.id}>{c.text}</li>)}</ul>
}
用于更新 UI 的 useOptimistic
'use client'
import { useOptimistic } from 'react'
export function TodoList({ todos }: { todos: Todo[] }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo: Todo) => [...state, newTodo]
)
async function addTodo(formData: FormData) {
const text = formData.get('text') as string
addOptimisticTodo({ id: crypto.randomUUID(), text, completed: false })
await createTodo(text)
}
return (
<form action={addTodo}>
<input name="text" />
{optimisticTodos.map(todo => <div key={todo.id}>{todo.text}</div>)}
</form>
)
}
Bundle 分析
# Install analyzer
npm install --save-dev @next/bundle-analyzer
# Run analysis
ANALYZE=true npm run build
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
modularizeImports: {
'lodash': { transform: 'lodash/{{member}}' },
},
})
性能检查清单
- 所有图片都使用
next/image并设置了适当的尺寸 - LCP 图片具有
priority属性 - 字体使用
next/font并指定子集 - 尽可能使用服务端组件
- 客户端组件仅用于叶节点
- 为数据获取设置 Suspense 边界
- 为开销较大的操作配置缓存
- 分析打包内容,检查是否存在重复项
- 对大型组件进行懒加载
- 使用 Lighthouse 验证优化前后的得分
常见错误
// ❌ DON'T: Fetch in useEffect
'use client'
useEffect(() => { fetch('/api/data').then(...) }, [])
// ✅ DO: Fetch directly in Server Component
const data = await fetch('/api/data')
// ❌ DON'T: Forget dimensions on images
<Image src="/photo.jpg" />
// ✅ DO: Always provide dimensions
<Image src="/photo.jpg" width={800} height={600} />
// ❌ DON'T: Use priority on all images
<Image src="/photo1.jpg" priority />
<Image src="/photo2.jpg" priority />
// ✅ DO: Priority only for LCP
<Image src="/hero.jpg" priority />
<Image src="/photo.jpg" loading="lazy" />
// ❌ DON'T: Cache everything with same TTL
{ revalidate: 3600 }
// ✅ DO: Match TTL to data change frequency
{ revalidate: 86400 } // Categories rarely change
{ revalidate: 60 } // Comments change often
外部资源
评论
支持 Markdown