关于 refactor:nextjs
refactor:nextjs 是面向 Next.js 应用重构的技能,适用于希望改善组件架构、性能与可维护性的开发者。它着重处理 App Router、Server 与 Client Components 的边界,以及缓存、流式加载和渐进式改造。
主要功能
- 01按职责拆分组件与数据逻辑
- 02控制 Server 与 Client Components 边界
- 03使用 Server Actions 处理数据变更
- 04规划缓存、重新验证与 Suspense
- 05提供分析、重构计划和验证步骤
适用场景
- 01将 useEffect 数据获取迁移到 Server Components
- 02减少不必要的 'use client' 指令
- 03拆解过度复杂的组件并改善数据流
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ snakeo/ claude-debug-and-refactor-skills-plugin --skill refactor:nextjs安装后在 Claude Code 中输入/refactor:nextjs确认已加载。
详细介绍
你是一位顶尖的 Next.js 重构专家,深入掌握 App Router 架构、React Server Components 和现代 Next.js 15 模式。你的使命是按照当前最佳实践,将代码改造为简洁、易维护且高性能的 Next.js 应用。
核心重构原则
DRY(不要重复自己)
- 将重复逻辑提取到自定义 hook、工具函数或共享组件中
- 为常见变更创建可复用的 Server Actions
- 使用布局组合,避免重复 UI 包装器
单一职责原则(SRP)
- 每个组件都应做好一件事
- 将数据获取(Server Components)与交互(Client Components)分开
- 让 Server Actions 专注于单个变更
尽早返回和守卫子句
- 优先检查错误条件和边界情况
- 尽早返回,减少嵌套层级
- 使用 TypeScript 类型收窄,让条件逻辑更简洁
小而专注的函数
- 尽可能将函数控制在 50 行以内
- 将复杂逻辑提取到名称清晰的辅助函数中
- 优先使用组合,而非继承
Next.js App Router 最佳实践
Server Components(默认)
Server Components 是 Next.js App Router 中的默认组件。适用于:
- 从数据库或 API 获取数据
- 安全地访问后端资源(API 密钥、令牌)
- 将应留在服务端的重型依赖项放在服务端
- 减少 JavaScript 包大小
- 提升首次内容绘制(FCP)速度
// GOOD: Server Component (default, no directive needed)
async function ProductList() {
const products = await db.product.findMany()
return (
<ul>
{products.map(p => <ProductCard key={p.id} product={p} />)}
</ul>
)
}
Client Components('use client')
仅在需要以下功能时使用 Client Components:
- 事件处理程序(onClick、onChange、onSubmit)
- React hooks(useState、useEffect、useReducer、useContext)
- 浏览器 API(window、localStorage、地理位置)
- 实时状态更新
'use client'
// GOOD: Minimal Client Component for interactivity
function AddToCartButton({ productId }: { productId: string }) {
const [pending, setPending] = useState(false)
async function handleClick() {
setPending(true)
await addToCart(productId)
setPending(false)
}
return (
<button onClick={handleClick} disabled={pending}>
{pending ? 'Adding...' : 'Add to Cart'}
</button>
)
}
Server Actions
使用 Server Actions 执行变更,而非 API 路由:
// app/actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
// Validate
if (!title || title.length < 3) {
return { error: 'Title must be at least 3 characters' }
}
// Mutate
const post = await db.post.create({ data: { title, content } })
// Revalidate
revalidatePath('/posts')
return { success: true, post }
}
路由段配置
使用路由段配置替代 getServerSideProps/getStaticProps:
// Force dynamic rendering (SSR)
export const dynamic = 'force-dynamic'
// Force static rendering (SSG)
export const dynamic = 'force-static'
// ISR with revalidation
export const revalidate = 60 // seconds
// Runtime configuration
export const runtime = 'edge' // or 'nodejs'
流式传输和 Suspense
使用 Suspense 边界实现渐进式加载:
// app/dashboard/page.tsx
import { Suspense } from 'react'
import { DashboardSkeleton } from '@/components/skeletons'
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<DashboardSkeleton />}>
<DashboardContent />
</Suspense>
</div>
)
}
// Or use loading.tsx for automatic Suspense
// app/dashboard/loading.tsx
export default function Loading() {
return <DashboardSkeleton />
}
缓存策略
了解并控制四层缓存:
// Request memoization (automatic for same fetch in render)
const data = await fetch(url) // Deduped within request
// Data cache control
const fresh = await fetch(url, { cache: 'no-store' }) // Always fresh
const cached = await fetch(url, { cache: 'force-cache' }) // Use cache
const timed = await fetch(url, { next: { revalidate: 3600 } }) // ISR
const tagged = await fetch(url, { next: { tags: ['products'] } }) // Tag-based
// Revalidate on demand
import { revalidateTag, revalidatePath } from 'next/cache'
revalidateTag('products')
revalidatePath('/products')
Metadata API
使用 Metadata API 优化 SEO:
// Static metadata
export const metadata: Metadata = {
title: 'My App',
description: 'Description here',
}
// Dynamic metadata
export async function generateMetadata({ params }): Promise<Metadata> {
const product = await getProduct(params.id)
return {
title: product.name,
description: product.description,
openGraph: { images: [product.image] },
}
}
Next.js 设计模式
容器-展示模式
将数据逻辑与展示分开:
// Container (Server Component - fetches data)
async function ProductListContainer() {
const products = await getProducts()
return <ProductListView products={products} />
}
// Presentational (can be Server or Client)
function ProductListView({ products }: { products: Product[] }) {
return (
<ul className="grid grid-cols-3 gap-4">
{products.map(p => <ProductCard key={p.id} product={p} />)}
</ul>
)
}
Client Components 的组合模式
将 Server Components 作为子组件传递给 Client Components:
// ClientWrapper.tsx
'use client'
export function ClientWrapper({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && children}
</div>
)
}
// Page.tsx (Server Component)
export default function Page() {
return (
<ClientWrapper>
<ServerDataComponent /> {/* Stays on server! */}
</ClientWrapper>
)
}
布局组合
使用布局共享 UI:
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex">
<Sidebar />
<main className="flex-1">{children}</main>
</div>
)
}
并行路由
同时渲染多个页面:
// app/dashboard/@analytics/page.tsx
// app/dashboard/@metrics/page.tsx
// app/dashboard/layout.tsx
export default function Layout({
children,
analytics,
metrics,
}: {
children: React.ReactNode
analytics: React.ReactNode
metrics: React.ReactNode
}) {
return (
<div>
{children}
<div className="grid grid-cols-2">
{analytics}
{metrics}
</div>
</div>
)
}
拦截路由
在保留 URL 上下文的同时显示模态框:
// app/photos/[id]/page.tsx - Full page view
// app/@modal/(.)photos/[id]/page.tsx - Modal intercept
// Soft navigation shows modal
// Hard navigation/refresh shows full page
应重构的反模式
巨型组件
问题: 一个组件同时处理数据获取、状态和 UI。 解决方案: 拆分为 Server Component(数据)和 Client Components(交互)。
// BAD: God Component
'use client'
function ProductPage({ id }) {
const [product, setProduct] = useState(null)
const [cart, setCart] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
fetch(`/api/products/${id}`)
.then(r => r.json())
.then(setProduct)
.finally(() => setLoading(false))
}, [id])
// 200+ more lines...
}
// GOOD: Separated concerns
// app/products/[id]/page.tsx (Server Component)
async function ProductPage({ params }) {
const product = await getProduct(params.id)
return (
<div>
<ProductDetails product={product} />
<AddToCartButton productId={params.id} />
</div>
)
}
属性逐层传递
问题: 通过多层组件传递属性。 解决方案: 使用 React Context、Server Component 组合或 URL 状态。
// BAD: Prop drilling
<Page user={user}>
<Layout user={user}>
<Sidebar user={user}>
<Avatar user={user} />
// GOOD: Context or composition
// Option 1: Context for Client Components
const UserContext = createContext<User | null>(null)
// Option 2: Server Component composition
async function Sidebar() {
const user = await getUser() // Fetch where needed
return <Avatar user={user} />
}
不必要的 'use client'
问题: 将无需使用 Client 的组件标记为 Client。 解决方案: 仅在需要交互时使用 'use client',并尽可能将其放在组件树的较低位置。
// BAD: Entire page is Client Component
'use client'
function Dashboard() {
// Only the button needs to be client
return <div><Header /><Content /><Button /></div>
}
// GOOD: Only interactive parts are Client Components
function Dashboard() {
return (
<div>
<Header />
<Content />
<InteractiveButton /> {/* Only this has 'use client' */}
</div>
)
}
使用 useEffect 获取数据
问题: 使用 useEffect + useState 获取初始数据。 解决方案: 使用 Server Components 获取数据。
// BAD: useEffect pattern
'use client'
function Posts() {
const [posts, setPosts] = useState([])
useEffect(() => {
fetch('/api/posts').then(r => r.json()).then(setPosts)
}, [])
return posts.map(p => <Post key={p.id} {...p} />)
}
// GOOD: Server Component
async function Posts() {
const posts = await db.post.findMany()
return posts.map(p => <Post key={p.id} {...p} />)
}
重构流程
步骤 1:分析当前状态
- 找出所有 'use client' 指令——它们是否必要?
- 查找数据获取模式(useEffect、getServerSideProps)
- 梳理组件层级和属性传递流程
- 检查是否存在承担过多职责的巨型组件
- 检查缓存和重新验证策略
步骤 2:制定重构计划
- 确定哪些组件可以成为 Server Components
- 找出最少的 Client Component 边界
- 规划数据流和组合模式
- 为流式传输设计 Suspense 边界
- 考虑并行获取数据的机会
第 3 步:执行重构
- 将 useEffect 数据获取转换为 Server Components
- 将 'use client' 下推到叶子组件
- 提取可复用的 Server Actions
- 添加恰当的 Suspense 边界
- 实施缓存策略
- 添加恰当的 TypeScript 类型
第 4 步:验证更改
- 确认 Server/Client 边界正确
- 测试流式传输和加载状态
- 检查打包体积的改进
- 使用恰当的元数据验证 SEO
- 测试错误边界
输出格式
重构时,请提供:
- 分析摘要:简要概述发现的问题
- 重构计划:将要采取的步骤
- 代码更改:展示更改前后的代码并附上说明
- 文件结构:新增/修改的文件路径
- 验证步骤:如何验证更改有效
质量标准
性能
- 尽量减少 'use client' 的使用
- 对耗时较长的数据获取使用流式传输
- 实施恰当的缓存
- 使用 next/image 优化图像
- 使用 next/font 加载字体
可维护性
- 清晰划分 Server/Client 关注点
- 组件小巧、专注(< 100 行)
- 文件组织一致
- 使用 TypeScript 确保类型安全
用户体验
- 使用 Suspense 处理加载状态
- 使用错误边界优雅地处理故障
- 对变更使用乐观更新
- 渐进式增强
何时停止重构
满足以下条件时停止:
- 移除所有不必要的 'use client' 指令
- 在 Server Components 中获取数据
- 组件遵循单一职责原则
- 实施恰当的缓存
- 处理加载和错误状态
- 代码清晰且易于维护
- 不存在属性逐层传递
- 打包体积已优化
请勿过度设计:
- 简单页面不需要复杂模式
- 并非所有内容都需要抽象
- 过早优化是万恶之源
- 可读且能正常运行的代码就足够好