关于 performance-optimization
performance-optimization 是帮助开发者提升 Next.js 应用性能的技能,内容涵盖 Core Web Vitals、渲染、缓存、打包体积和数据库查询优化。
主要功能
- 01Core Web Vitals 优化建议
- 02图像与字体优化
- 03动态导入和代码拆分
- 04缓存与标签重新验证
- 05打包分析和 tree shaking
- 06数据库查询与渲染策略
适用场景
- 01排查页面加载缓慢问题
- 02减少 JavaScript 打包体积
- 03优化图像与字体加载
- 04调整数据获取和数据库查询
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ jmsktm/ claude-settings --skill performance-optimization安装后在 Claude Code 中输入/performance-optimization确认已加载。
详细介绍
性能优化技能
概述
此技能可帮助你优化 Next.js 应用,以实现最佳性能。从图像优化到代码拆分,涵盖了取得出色 Core Web Vitals 评分所需的所有技巧。
Core Web Vitals
1. 最大内容绘制(LCP)
目标:< 2.5s
优化方法:
- 使用
next/image处理图像 - 实施适当的缓存
- 使用 CDN 提供静态资源
- 优化服务器响应时间
- 减少阻塞渲染的资源
2. 首次输入延迟(FID)/ 与下一次绘制的交互(INP)
目标:< 100ms / < 200ms
优化方法:
- 尽量减少 JavaScript 执行
- 对大型捆绑包进行代码拆分
- 使用 Web Workers 处理繁重任务
- 延迟加载非关键 JavaScript
- 优化事件处理程序
3. 累积布局偏移(CLS)
目标:< 0.1
优化方法:
- 设置图像尺寸
- 为广告预留空间
- 避免在现有内容上方插入内容
- 使用
transform代替布局属性
图像优化
Next.js 图像组件
import Image from 'next/image'
// ✅ Optimized
export function OptimizedImage() {
return (
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // For above-fold images
quality={85} // Default: 75
placeholder="blur"
blurDataURL="data:image/..." // Or import for static
/>
)
}
// For external images, configure domains
// next.config.js
module.exports = {
images: {
domains: ['example.com'],
// Or use remotePatterns for more control
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
},
],
},
}
响应式图像
<Image
src="/hero.jpg"
alt="Hero"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
style={{ objectFit: 'cover' }}
priority
/>
图像格式
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
},
}
字体优化
使用 next/font
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body className="font-sans">{children}</body>
</html>
)
}
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
mono: ['var(--font-roboto-mono)'],
},
},
},
}
本地字体
import localFont from 'next/font/local'
const customFont = localFont({
src: './fonts/custom-font.woff2',
display: 'swap',
variable: '--font-custom',
})
代码拆分
动态导入
import dynamic from 'next/dynamic'
// Load component only when needed
const HeavyComponent = dynamic(() => import('@/components/heavy-component'), {
loading: () => <p>Loading...</p>,
ssr: false, // Disable SSR for this component
})
export function Page() {
return (
<div>
<HeavyComponent />
</div>
)
}
条件加载
'use client'
import { useState } from 'react'
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('@/components/chart'), {
ssr: false,
})
export function Dashboard() {
const [showChart, setShowChart] = useState(false)
return (
<div>
<button onClick={() => setShowChart(true)}>Show Chart</button>
{showChart && <Chart />}
</div>
)
}
命名导出
const ComponentA = dynamic(() =>
import('@/components/bundle').then((mod) => mod.ComponentA)
)
React 优化
React.memo
import { memo } from 'react'
// Only re-renders if props change
const ExpensiveComponent = memo(function ExpensiveComponent({
data,
}: {
data: Data
}) {
return <div>{/* Expensive rendering */}</div>
})
// Custom comparison
const MemoizedComponent = memo(
Component,
(prevProps, nextProps) => {
return prevProps.id === nextProps.id
}
)
useMemo
'use client'
import { useMemo } from 'react'
export function DataTable({ items }: { items: Item[] }) {
// Only recalculate when items change
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name))
}, [items])
return (
<table>
{sortedItems.map((item) => (
<tr key={item.id}>
<td>{item.name}</td>
</tr>
))}
</table>
)
}
useCallback
'use client'
import { useCallback, useState } from 'react'
export function Parent() {
const [count, setCount] = useState(0)
// Stable function reference
const handleClick = useCallback(() => {
console.log('clicked')
}, [])
return <Child onClick={handleClick} />
}
缓存策略
API 路由缓存
// app/api/data/route.ts
export async function GET() {
const data = await fetchData()
return Response.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=30',
},
})
}
数据获取缓存
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 },
})
// Never cache (always fresh)
const data = await fetch('https://api.example.com/data', {
cache: 'no-store',
})
// Cache forever
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache',
})
基于标签的重新验证
// Tag the fetch
const data = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] },
})
// Revalidate all 'posts' fetches
import { revalidateTag } from 'next/cache'
export async function POST() {
// Mutate data
await createPost()
// Revalidate
revalidateTag('posts')
}
捆绑包优化
分析捆绑包
# Add to package.json
{
"scripts": {
"analyze": "ANALYZE=true next build"
}
}
# Install bundle analyzer
npm install @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// Your config
})
Tree Shaking
// ❌ Bad - Imports entire library
import _ from 'lodash'
// ✅ Good - Only imports what you need
import debounce from 'lodash/debounce'
移除未使用的依赖项
# Find unused dependencies
npx depcheck
# Remove them
npm uninstall unused-package
流式传输与 Suspense
流式传输组件
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
const data = await slowFetch()
return <div>{data}</div>
}
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<Loading />}>
<SlowComponent />
</Suspense>
</div>
)
}
多个 Suspense 边界
export default function Page() {
return (
<div>
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<Content />
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
</div>
)
}
数据库查询优化
高效使用 Prisma
// ❌ Bad - N+1 query problem
const users = await prisma.user.findMany()
for (const user of users) {
const posts = await prisma.post.findMany({ where: { userId: user.id } })
}
// ✅ Good - Single query with include
const users = await prisma.user.findMany({
include: {
posts: true,
},
})
// ✅ Better - Select only what you need
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
posts: {
select: {
id: true,
title: true,
},
},
},
})
数据库索引
model Post {
id String @id @default(cuid())
title String
userId String
createdAt DateTime @default(now())
// Add indexes for frequently queried fields
@@index([userId])
@@index([createdAt])
@@index([userId, createdAt])
}
预渲染策略
静态站点生成(SSG)
// Fully static - generated at build time
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>{/* Render data */}</div>
}
增量静态再生成(ISR)
// Revalidate every 60 seconds
export const revalidate = 60
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>{/* Render data */}</div>
}
动态渲染
// Force dynamic rendering
export const dynamic = 'force-dynamic'
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'no-store',
})
return <div>{/* Render data */}</div>
}
懒加载
图像
<Image
src="/image.jpg"
alt="Image"
width={500}
height={300}
loading="lazy" // Default behavior
/>
滚动时加载组件
'use client'
import { useEffect, useState, useRef } from 'react'
import dynamic from 'next/dynamic'
const HeavyComponent = dynamic(() => import('@/components/heavy'))
export function LazySection() {
const [isVisible, setIsVisible] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true)
observer.disconnect()
}
},
{ threshold: 0.1 }
)
if (ref.current) {
observer.observe(ref.current)
}
return () => observer.disconnect()
}, [])
return (
<div ref={ref}>
{isVisible ? <HeavyComponent /> : <div>Loading...</div>}
</div>
)
}
性能监控
衡量性能
'use client'
import { useEffect } from 'react'
export function PerformanceMonitor() {
useEffect(() => {
// Measure LCP
new PerformanceObserver((list) => {
const entries = list.getEntries()
const lastEntry = entries[entries.length - 1]
console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime)
}).observe({ type: 'largest-contentful-paint', buffered: true })
// Measure FID
new PerformanceObserver((list) => {
const entries = list.getEntries()
entries.forEach((entry) => {
console.log('FID:', entry.processingStart - entry.startTime)
})
}).observe({ type: 'first-input', buffered: true })
// Measure CLS
let clsScore = 0
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsScore += entry.value
}
}
console.log('CLS:', clsScore)
}).observe({ type: 'layout-shift', buffered: true })
}, [])
return null
}
最佳实践检查清单
- 所有图像均使用
next/image - 使用
next/font优化字体 - 对大型组件实施代码拆分
- 对开销较大的组件使用 React.memo
- 实施适当的缓存策略
- 设置 Suspense 边界
- 优化数据库查询
- 为半静态内容启用 ISR
- 懒加载首屏以下的内容
- 监控 Core Web Vitals
- 尽量减小 JavaScript 捆绑包
- 使用 CDN 提供静态资源
- 实施适当的加载状态
- 在慢速 3G 网络连接下进行测试
何时使用此技能
在以下情况调用此技能:
- 优化页面加载时间
- 提高 Core Web Vitals 评分
- 减小捆绑包体积
- 调试性能问题
- 设置缓存策略
- 实施懒加载
- 优化图像或字体
- 提升数据库查询性能
评论
支持 Markdown