| name | performance-optimizer |
| description | Optimize web performance including Core Web Vitals, bundle size, and runtime efficiency. Analyze and improve LCP, FID, CLS metrics. Use when optimizing performance, reducing bundle size, or when the user mentions slow, performance, loading, lighthouse, or web vitals. |
Performance Optimizer (性能优化师)
Goal: 提升 Web 应用性能,确保良好用户体验和 SEO 表现。
核心指标: Core Web Vitals (LCP, FID, CLS)
1. Core Web Vitals
指标标准
| 指标 | 含义 | Good | Needs Improvement | Poor |
|---|
| LCP | 最大内容绘制 | < 2.5s | 2.5s - 4s | > 4s |
| FID | 首次输入延迟 | < 100ms | 100ms - 300ms | > 300ms |
| CLS | 累积布局偏移 | < 0.1 | 0.1 - 0.25 | > 0.25 |
| INP | 交互到下一次绘制 | < 200ms | 200ms - 500ms | > 500ms |
测量工具
npx lighthouse https://example.com --view
pnpm add web-vitals
import { onLCP, onFID, onCLS, onINP } from 'web-vitals'
onLCP(console.log)
onFID(console.log)
onCLS(console.log)
onINP(console.log)
2. Next.js Optimizations
Image 优化
<img src="/hero.png" />
import Image from 'next/image'
<Image
src="/hero.png"
width={800}
height={400}
alt="Hero"
priority
/>
Font 优化
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
preload: true,
})
export default function Layout({ children }) {
return (
<html className={inter.className}>
<body>{children}</body>
</html>
)
}
Script 优化
import Script from 'next/script'
<Script
src="https://analytics.com/script.js"
strategy="lazyOnload"
/>
<Script
src="https://widget.com/chat.js"
strategy="afterInteractive"
/>
动态导入
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <Skeleton />,
ssr: false,
})
const AdminPanel = dynamic(() => import('./AdminPanel'), {
loading: () => null,
})
function Page() {
return (
<>
{isAdmin && <AdminPanel />}
</>
)
}
3. Bundle Size Optimization
分析 Bundle
pnpm add -D @next/bundle-analyzer
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// config
})
ANALYZE=true pnpm build
Tree Shaking
import _ from 'lodash'
_.debounce(fn, 300)
import debounce from 'lodash/debounce'
debounce(fn, 300)
import { debounce } from 'lodash-es'
替换重型库
| 重型库 | 轻量替代 | 节省 |
|---|
| moment.js | date-fns / dayjs | ~95% |
| lodash | lodash-es + 按需导入 | ~90% |
| axios | fetch (原生) | 100% |
| uuid | nanoid | ~75% |
代码分割
const Modal = dynamic(() => import('./Modal'))
if (condition) {
const module = await import('./heavy-module')
}
4. React Optimizations
避免不必要的重渲染
<Child style={{ color: 'red' }} />
const style = useMemo(() => ({ color: 'red' }), [])
<Child style={style} />
<Button onClick={() => handleClick(id)} />
const onClick = useCallback(() => handleClick(id), [id])
<Button onClick={onClick} />
React.memo
const UserCard = memo(function UserCard({ user }) {
return <div>{user.name}</div>
})
const UserCard = memo(function UserCard({ user }) {
return <div>{user.name}</div>
}, (prev, next) => prev.user.id === next.user.id)
虚拟化长列表
import { useVirtualizer } from '@tanstack/react-virtual'
function VirtualList({ items }) {
const parentRef = useRef(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
})
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: virtualItem.start,
height: virtualItem.size,
}}
>
{items[virtualItem.index]}
</div>
))}
</div>
</div>
)
}
5. Network Optimizations
预加载关键资源
export default function Layout() {
return (
<html>
<head>
<link rel="preconnect" href="https://api.openai.com" />
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />
</head>
</html>
)
}
缓存策略
export async function GET() {
return Response.json(data, {
headers: {
'Cache-Control': 'public, max-age=60, stale-while-revalidate=300',
},
})
}
const data = await fetch(url, {
next: { revalidate: 60 },
})
const { data } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 60 * 1000,
})
压缩
module.exports = {
compress: true,
experimental: {
serverActions: {
bodySizeLimit: '2mb',
},
},
}
6. CLS Prevention
图片尺寸
<img src="/photo.jpg" />
<Image src="/photo.jpg" width={400} height={300} alt="" />
<div style={{ aspectRatio: '16/9' }}>
<Image src="/photo.jpg" fill alt="" />
</div>
字体加载
@font-face {
font-family: 'Custom';
src: url('/font.woff2') format('woff2');
font-display: swap;
}
动态内容
{isLoaded && <Banner />}
<div style={{ minHeight: '100px' }}>
{isLoaded ? <Banner /> : <Skeleton />}
</div>
7. Performance Monitoring
Vercel Analytics
import { Analytics } from '@vercel/analytics/react'
import { SpeedInsights } from '@vercel/speed-insights/next'
export default function Layout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
)
}
自定义监控
import { onLCP, onFID, onCLS } from 'web-vitals'
function sendToAnalytics(metric) {
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify(metric),
})
}
onLCP(sendToAnalytics)
onFID(sendToAnalytics)
onCLS(sendToAnalytics)
8. Performance Checklist
## 性能优化检查清单
### 加载性能 (LCP)
- [ ] 关键图片使用 next/image + priority
- [ ] 字体使用 next/font
- [ ] 首屏内容优先加载
- [ ] 使用 CDN
### 交互性能 (FID/INP)
- [ ] 减少主线程阻塞
- [ ] 使用 React.memo 避免重渲染
- [ ] 长列表虚拟化
- [ ] 代码分割
### 视觉稳定性 (CLS)
- [ ] 图片有明确尺寸
- [ ] 使用 font-display: swap
- [ ] 动态内容预留空间
### Bundle 优化
- [ ] 按需导入
- [ ] 动态导入非关键组件
- [ ] 替换重型库
- [ ] 分析 bundle 大小
### 网络优化
- [ ] 使用缓存策略
- [ ] 预连接关键域名
- [ ] 开启压缩
Quick Reference
Lighthouse 目标
Performance: > 90
Accessibility: > 90
Best Practices: > 90
SEO: > 90
常用命令
ANALYZE=true pnpm build
npx lighthouse https://example.com
npx bundlephobia lodash
性能预算
JS Bundle: < 200KB (gzipped)
First Load: < 100KB
LCP: < 2.5s
TTI: < 3.5s