mcpField
N
nextjs

nextjs

github.com/jezweb开发1.1k1.0k 次安装3 周前更新
代码仓库

关于 nextjs

nextjs 技能提供面向生产环境的 Next.js App Router 指引,重点涵盖 Next.js 16 变更和迁移问题。适合开发或升级 App Router 应用的开发者,并附有示例、模板和故障排查资料。

主要功能

  1. 01说明 Next.js 16 重大变更
  2. 02指导从 Next.js 15 迁移
  3. 03介绍 Cache Components 与缓存 API
  4. 04记录路由处理器和 proxy 变更
  5. 05提供模板和错误解决方案
  6. 06汇总安全公告与版本要求

适用场景

  1. 01将 App Router 项目迁移到 Next.js 16
  2. 02更新异步路由 API 的代码
  3. 03配置缓存和并行路由
  4. 04排查文档涵盖的 Next.js 错误

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jezweb/claude-skills --skill nextjs

安装后在 Claude Code 中输入/nextjs确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
.claude-plugin/
plugin.json
commands/
init.md
references/
next-16-migration-guide.md
top-errors.md
rules/
nextjs.md
scripts/
check-versions.sh
templates/
app-router-async-params.tsx
cache-component-use-cache.tsx
package.json
parallel-routes-with-default.tsx
proxy-migration.ts
route-handler-api.ts
server-actions-form.tsx

详细介绍

Next.js App Router - 生产模式

版本:Next.js 16.1.1 React 版本:19.2.3 Node.js:20.9+ 最后验证时间:2026-01-09


目录

  1. 何时使用此技能
  2. 何时不使用此技能
  3. 安全公告(2025 年 12 月)
  4. Next.js 16.1 更新
  5. Next.js 16 重大变更
  6. 缓存组件与缓存 API
  7. 路由处理程序(Next.js 16 更新)
  8. Proxy 与 Middleware
  9. 并行路由 - 必须提供 default.js
  10. React 19.2 功能
  11. Turbopack(在 Next.js 16 中稳定)
  12. 常见错误与解决方案
  13. 模板与资源

何时使用此技能

重点:Next.js 16 的重大变更与知识盲区(2024 年 12 月起)。

在以下情况下使用此技能:

  • Next.js 16 重大变更(异步 params、proxy.ts、并行路由 default.js、已移除的功能)
  • 使用 "use cache" 指令的 缓存组件(Next.js 16 中新增)
  • 新的缓存 API:revalidateTag()、updateTag()、refresh()(在 Next.js 16 中更新)
  • 从 Next.js 15 迁移到 16(避免重大变更错误)
  • 异步路由参数(params、searchParams、cookies()、headers() 现在均为异步)
  • 带 default.js 的并行路由(Next.js 16 中必需)
  • React 19.2 功能(视图过渡、useEffectEvent()、React Compiler)
  • Turbopack(在 Next.js 16 中已稳定并设为默认)
  • 图像默认值已更改(Next.js 16 中的 TTL、尺寸和质量)
  • 错误预防(记录了 25 个 Next.js 16 错误及其解决方案)

何时不使用此技能

以下情况请勿使用此技能:

  • 部署到 Cloudflare Workers → 请改用 cloudflare-nextjs 技能
  • Pages Router 模式 → 此技能仅涵盖 App Router(Pages Router 已过时)
  • 身份验证库 → 请使用 clerk-auth、better-auth 或其他身份验证专用技能
  • 数据库集成 → 请使用 cloudflare-d1、drizzle-orm-d1 或数据库专用技能
  • UI 组件库 → Tailwind + shadcn/ui 请使用 tailwind-v4-shadcn 技能
  • 状态管理 → 请使用 zustand-state-management、tanstack-query 技能
  • 表单库 → 请使用 react-hook-form-zod 技能
  • Vercel 专属功能 → 请参阅 Vercel 平台文档
  • Next.js Enterprise 功能(ISR、DPR)→ 请参阅 Next.js Enterprise 文档
  • 部署配置 → 请使用平台专用的部署技能

与其他技能的关系:

  • cloudflare-nextjs:用于将 Next.js 部署到 Cloudflare Workers(若部署到 Cloudflare,请同时使用这两项技能)
  • tailwind-v4-shadcn:用于配置 Tailwind v4 + shadcn/ui(可与此技能搭配使用)
  • clerk-auth:用于在 Next.js 中使用 Clerk 进行身份验证(可与此技能搭配使用)
  • better-auth:用于集成 Better Auth(可与此技能搭配使用)

安全公告(2025 年 12 月)

严重:2025 年 12 月披露了三个影响使用 React Server Components 的 Next.js 版本的安全漏洞:

CVE严重程度受影响版本描述
CVE-2025-66478严重(10.0)15.x、16.xServer Component 任意代码执行
CVE-2025-55184高13.x-16.x通过格式错误的请求造成拒绝服务
CVE-2025-55183中13.x-16.x错误响应中暴露源代码

必须采取的措施:立即升级到 Next.js 16.1.1 或更高版本。

npm update next
# Verify: npm list next should show 16.1.1+

参考资料:


Next.js 16.1 更新(2025 年 12 月)

16.1 新增功能:

  • Turbopack 文件系统缓存(稳定版):现已在开发环境中默认启用
  • Next.js Bundle Analyzer:用于分析捆绑包的新实验性功能
  • 调试功能改进:增强了对 next dev --inspect 的支持
  • 安全修复:修复了 CVE-2025-66478、CVE-2025-55184、CVE-2025-55183

Next.js 16 重大变更

重要:Next.js 16 引入了多项重大变更。如果要从 Next.js 15 或更早版本迁移,请仔细阅读本节。

1. 异步路由参数(重大变更)

重大变更:params、searchParams、cookies()、headers()、draftMode() 现在都是异步的,必须使用 await。

之前(Next.js 15):

// ❌ This no longer works in Next.js 16
export default function Page({ params, searchParams }: {
  params: { slug: string }
  searchParams: { query: string }
}) {
  const slug = params.slug // ❌ Error: params is a Promise
  const query = searchParams.query // ❌ Error: searchParams is a Promise
  return <div>{slug}</div>
}

之后(Next.js 16):

// ✅ Correct: await params and searchParams
export default async function Page({ params, searchParams }: {
  params: Promise<{ slug: string }>
  searchParams: Promise<{ query: string }>
}) {
  const { slug } = await params // ✅ Await the promise
  const { query } = await searchParams // ✅ Await the promise
  return <div>{slug}</div>
}

适用范围:

  • 页面、布局、路由处理程序中的 params
  • 页面中的 searchParams
  • 来自 next/headers 的 cookies()
  • 来自 next/headers 的 headers()
  • 来自 next/headers 的 draftMode()

迁移:

// ❌ Before
import { cookies, headers } from 'next/headers'

export function MyComponent() {
  const cookieStore = cookies() // ❌ Sync access
  const headersList = headers() // ❌ Sync access
}

// ✅ After
import { cookies, headers } from 'next/headers'

export async function MyComponent() {
  const cookieStore = await cookies() // ✅ Async access
  const headersList = await headers() // ✅ Async access
}

Codemod:运行 npx @next/codemod@canary upgrade latest 可自动完成迁移。

Codemod 的局限性(来自社区): 官方 codemod 可处理约 80% 的异步 API 迁移,但无法处理一些边缘情况:

  • 在自定义钩子中访问异步 API
  • 通过条件逻辑访问 params
  • 从外部包导入组件
  • 包含多个异步调用的复杂服务器操作

运行 codemod 后,请搜索 @next-codemod-error 注释,查找无法自动修复的位置。

客户端组件的手动迁移:

// For client components, use React.use() to unwrap promises
'use client';

import { use } from 'react';

export default function ClientComponent({
  params
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = use(params); // Unwrap Promise in client
  return <div>{id}</div>;
}

参见模板:templates/app-router-async-params.tsx


2. Middleware → Proxy 迁移(重大变更)

重大变更:Next.js 16 中的 middleware.ts 已弃用。请改用 proxy.ts。

变更原因:proxy.ts 在 Node.js 运行时(而非 Edge 运行时)运行,从而明确标示网络边界。这能更清晰地区分边缘中间件与服务器端代理。

迁移步骤:

  1. 重命名文件:middleware.ts → proxy.ts
  2. 重命名函数:middleware → proxy
  3. 更新配置:matcher → config.matcher(语法不变)

之前(Next.js 15):

// middleware.ts ❌ Deprecated in Next.js 16
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  response.headers.set('x-custom-header', 'value')
  return response
}

export const config = {
  matcher: '/api/:path*',
}

之后(Next.js 16):

// proxy.ts ✅ New in Next.js 16
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function proxy(request: NextRequest) {
  const response = NextResponse.next()
  response.headers.set('x-custom-header', 'value')
  return response
}

export const config = {
  matcher: '/api/:path*',
}

注意:middleware.ts 在 Next.js 16 中仍可使用,但已弃用。请迁移到 proxy.ts,以确保未来的兼容性。

参见模板:templates/proxy-migration.ts 参见参考文档:references/proxy-vs-middleware.md


3. 并行路由必须包含 default.js(重大变更)

重大变更:并行路由现在必须包含显式的 default.js 文件。缺少该文件时,软导航期间路由会失败。

结构:

app/
├── @auth/
│   ├── login/
│   │   └── page.tsx
│   └── default.tsx    ← REQUIRED in Next.js 16
├── @dashboard/
│   ├── overview/
│   │   └── page.tsx
│   └── default.tsx    ← REQUIRED in Next.js 16
└── layout.tsx

布局:

// app/layout.tsx
export default function Layout({
  children,
  auth,
  dashboard,
}: {
  children: React.ReactNode
  auth: React.ReactNode
  dashboard: React.ReactNode
}) {
  return (
    <html>
      <body>
        {auth}
        {dashboard}
        {children}
      </body>
    </html>
  )
}

默认回退(必需):

// app/@auth/default.tsx
export default function AuthDefault() {
  return null // or <Skeleton /> or redirect
}

// app/@dashboard/default.tsx
export default function DashboardDefault() {
  return null
}

必须包含的原因:Next.js 16 更改了并行路由处理软导航的方式。缺少 default.js 时,客户端导航期间未匹配的插槽会报错。

参见模板:templates/parallel-routes-with-default.tsx


4. 已移除的功能(重大变更)

以下功能已在 Next.js 16 中移除:

  1. AMP 支持 —— 已完全移除。请迁移到标准页面。
  2. next lint 命令 —— 请直接使用 ESLint 或 Biome。
  3. serverRuntimeConfig 和 publicRuntimeConfig —— 请改用环境变量。
  4. experimental.ppr 标志 —— 已发展为 Cache Components。请使用 "use cache" 指令。
  5. 自动设置 scroll-behavior: smooth —— 如有需要,请手动添加。
  6. Node.js 18 支持 —— 最低版本现为 20.9+。

迁移:

  • AMP:将 AMP 页面转换为标准页面,或使用单独的 AMP 实现。
  • Lint:直接运行 npx eslint . 或 npx biome lint .。
  • 配置:将 serverRuntimeConfig 替换为 process.env.VARIABLE。
  • PPR:从 experimental.ppr 迁移到 "use cache" 指令(参见 Cache Components 部分)。

5. 版本要求(不兼容变更)

Next.js 16 要求:

  • Node.js:20.9+(不再支持 Node.js 18)
  • TypeScript:5.1+(如果使用 TypeScript)
  • React:19.2+(随 Next.js 16 自动安装)
  • 浏览器:Chrome 111+、Safari 16.4+、Firefox 109+、Edge 111+

检查版本:

node --version    # Should be 20.9+
npm --version     # Should be 10+
npx next --version # Should be 16.0.0+

升级 Node.js:

# Using nvm
nvm install 20
nvm use 20
nvm alias default 20

# Using Homebrew (macOS)
brew install node@20

# Using apt (Ubuntu/Debian)
sudo apt update
sudo apt install nodejs npm

6. 图片默认值变更(不兼容变更)

Next.js 16 更改了 next/image 的默认值:

设置Next.js 15Next.js 16
TTL(缓存时长)60 秒4 小时
imageSizes[16, 32, 48, 64, 96, 128, 256, 384][640, 750, 828, 1080, 1200](减少)
qualities[75, 90, 100][75](单一画质)

影响:

  • 图片缓存时间更长(4 小时,而非 60 秒)
  • 生成的图片尺寸更少(构建体积更小,但粒度更粗)
  • 生成单一画质(75),而非多种画质

覆盖默认值(如有需要):

// next.config.ts
import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    minimumCacheTTL: 60, // Revert to 60 seconds
    deviceSizes: [640, 750, 828, 1080, 1200, 1920], // Add larger sizes
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // Restore old sizes
    formats: ['image/webp'], // Default
  },
}

export default config

参见模板:templates/image-optimization.tsx


缓存组件与缓存 API

Next.js 16 新增:缓存组件通过 "use cache" 指令引入选择性缓存,取代 Next.js 15 中的隐式缓存。

1. 概述

变更内容:

  • Next.js 15:隐式缓存(默认缓存所有服务器组件)
  • Next.js 16:使用 "use cache" 指令进行选择性缓存

变更原因:显式缓存让开发者拥有更多控制权,并使缓存行为更可预测。

重要的默认缓存行为(来源于社区):

功能Next.js 14Next.js 15/16
fetch() 请求默认缓存默认不缓存
路由器缓存(动态页面)在客户端缓存默认不缓存
路由器缓存(静态页面)缓存仍然缓存
路由处理程序(GET)缓存默认动态

最佳实践:在 Next.js 16 中默认采用动态渲染。先不启用缓存,再在有益的地方添加缓存,而不是排查意外的缓存命中。务必使用生产构建进行测试——开发服务器的行为有所不同。

缓存组件支持:

  • 组件级缓存(缓存特定组件,而非整个页面)
  • 函数级缓存(缓存开销较大的计算)
  • 页面级缓存(有选择地缓存整个页面)
  • 部分预渲染(PPR)——缓存静态部分,按需渲染动态部分

2. "use cache" 指令

语法:在服务器组件、函数或路由处理程序的顶部添加 "use cache"。

组件级缓存:

// app/components/expensive-component.tsx
'use cache'

export async function ExpensiveComponent() {
  const data = await fetch('https://api.example.com/data')
  const json = await data.json()

  return (
    <div>
      <h1>{json.title}</h1>
      <p>{json.description}</p>
    </div>
  )
}

函数级缓存:

// lib/data.ts
'use cache'

export async function getExpensiveData(id: string) {
  const response = await fetch(`https://api.example.com/items/${id}`)
  return response.json()
}

// Usage in component
import { getExpensiveData } from '@/lib/data'

export async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const product = await getExpensiveData(id) // Cached

  return <div>{product.name}</div>
}

页面级缓存:

// app/blog/[slug]/page.tsx
'use cache'

export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json())
  return posts.map((post: { slug: string }) => ({ slug: post.slug }))
}

export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const post = await fetch(`https://api.example.com/posts/${slug}`).then(r => r.json())

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  )
}

参见模板:templates/cache-component-use-cache.tsx


3. 部分预渲染(PPR)

PPR 允许缓存页面的静态部分,同时按需渲染动态部分。

模式:

// app/dashboard/page.tsx

// Static header (cached)
'use cache'
async function StaticHeader() {
  return <header>My App</header>
}

// Dynamic user info (not cached)
async function DynamicUserInfo() {
  const cookieStore = await cookies()
  const userId = cookieStore.get('userId')?.value
  const user = await fetch(`/api/users/${userId}`).then(r => r.json())

  return <div>Welcome, {user.name}</div>
}

// Page combines both
export default function Dashboard() {
  return (
    <div>
      <StaticHeader /> {/* Cached */}
      <DynamicUserInfo /> {/* Dynamic */}
    </div>
  )
}

适用场景:

  • 页面同时包含静态和动态内容
  • 需要缓存布局、页眉和页脚,同时渲染用户专属内容
  • 需要快速初始加载(静态部分)和个性化内容(动态部分)

参见参考资料:references/cache-components-guide.md


4. revalidateTag()——更新后的 API

不兼容变更:revalidateTag() 现在需要第二个参数(cacheLife 配置),以实现 stale-while-revalidate 行为。

之前(Next.js 15):

import { revalidateTag } from 'next/cache'

export async function updatePost(id: string) {
  await fetch(`/api/posts/${id}`, { method: 'PATCH' })
  revalidateTag('posts') // ❌ Only one argument in Next.js 15
}

之后(Next.js 16):

import { revalidateTag } from 'next/cache'

export async function updatePost(id: string) {
  await fetch(`/api/posts/${id}`, { method: 'PATCH' })
  revalidateTag('posts', 'max') // ✅ Second argument required in Next.js 16
}

内置缓存生命周期配置:

  • 'max'——最大陈旧时间(大多数用例推荐)
  • 'hours'——数小时后变为陈旧
  • 'days'——数天后变为陈旧
  • 'weeks'——数周后变为陈旧
  • 'default'——默认缓存行为

自定义缓存生命周期配置:

revalidateTag('posts', {
  stale: 3600, // Stale after 1 hour (seconds)
  revalidate: 86400, // Revalidate every 24 hours (seconds)
  expire: false, // Never expire (optional)
})

服务器操作中的模式:

'use server'

import { revalidateTag } from 'next/cache'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  await fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify({ title, content }),
  })

  revalidateTag('posts', 'max') // ✅ Revalidate with max staleness
}

参见模板:templates/revalidate-tag-cache-life.ts


5. updateTag() - 新 API(仅限 Server Actions)

Next.js 16 新增:updateTag() 为 Server Actions 提供读己之写语义。

功能:

  • 立即使缓存过期
  • 在同一请求中刷新数据
  • 突变后立即显示更新后的数据(无陈旧数据)

与 revalidateTag() 的区别:

  • revalidateTag():过期时重新验证(显示陈旧数据,并在后台重新验证)
  • updateTag():立即刷新(使缓存过期,并在同一请求中获取新数据)

使用场景:表单、用户设置,或用户期望立即获得反馈的任何突变操作。

模式:

'use server'

import { updateTag } from 'next/cache'

export async function updateUserProfile(formData: FormData) {
  const name = formData.get('name') as string
  const email = formData.get('email') as string

  // Update database
  await db.users.update({ name, email })

  // Immediately refresh cache (read-your-writes)
  updateTag('user-profile')

  // User sees updated data immediately (no stale data)
}

使用时机:

  • updateTag():用户设置、个人资料更新、关键突变操作(立即反馈)
  • revalidateTag():博客文章、产品列表、非关键更新(后台重新验证)

参见模板:templates/server-action-update-tag.ts


6. refresh() - 新 API(仅限 Server Actions)

Next.js 16 新增:refresh() 只刷新未缓存的数据(与客户端的 router.refresh() 互为补充)。

使用时机:

  • 刷新动态数据,而不影响已缓存的数据
  • 在服务端配合使用 router.refresh()

模式:

'use server'

import { refresh } from 'next/cache'

export async function refreshDashboard() {
  // Refresh uncached data (e.g., real-time metrics)
  refresh()

  // Cached data (e.g., static header) remains cached
}

与 revalidateTag() 和 updateTag() 的区别:

  • refresh():只刷新未缓存的数据
  • revalidateTag():重新验证带有特定标签的数据(过期时重新验证)
  • updateTag():立即使带有特定标签的数据过期并刷新

参见参考文档:references/cache-components-guide.md



路由处理器(Next.js 16 更新)

路由处理器中的异步参数(重大变更)

重要:在 Next.js 16 路由处理器中,params 和 headers() 现在是异步的。

示例:

// app/api/posts/[id]/route.ts
import { NextResponse } from 'next/server'
import { headers } from 'next/headers'

export async function GET(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params // ✅ Await params in Next.js 16
  const headersList = await headers() // ✅ Await headers in Next.js 16

  const post = await db.posts.findUnique({ where: { id } })

  return NextResponse.json(post)
}

参见模板:templates/route-handler-api.ts


Proxy 与 Middleware

Next.js 16 引入了 proxy.ts,用于替代 middleware.ts。

为什么要更改?
  • middleware.ts:在 Edge 运行时运行(Node.js API 受限)
  • proxy.ts:在 Node.js 运行时运行(可使用完整的 Node.js API)

新的 proxy.ts 明确了网络边界,并提供了更大的灵活性。

迁移

之前(middleware.ts):

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Check auth
  const token = request.cookies.get('token')

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: '/dashboard/:path*',
}

之后(proxy.ts):

// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function proxy(request: NextRequest) {
  // Check auth
  const token = request.cookies.get('token')

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: '/dashboard/:path*',
}

参见模板:templates/proxy-migration.ts 参见参考文档:references/proxy-vs-middleware.md


并行路由 - 必须提供 default.js(重大变更)

Next.js 16 中的重大变更:并行路由现在必须显式提供 default.js 文件。

结构:

app/
├── @modal/
│   ├── login/page.tsx
│   └── default.tsx  ← REQUIRED in Next.js 16
├── @feed/
│   ├── trending/page.tsx
│   └── default.tsx  ← REQUIRED in Next.js 16
└── layout.tsx

默认文件(必需):

// app/@modal/default.tsx
export default function ModalDefault() {
  return null // or <Skeleton /> or redirect
}

必须提供的原因:Next.js 16 更改了软导航的处理方式。如果没有 default.js,客户端导航期间未匹配的插槽会报错。

高级边缘情况(来自社区): 即使有 default.js 文件,对含有并行路由的路由执行硬导航或刷新,也可能返回 404 错误。解决方法是添加一个捕获所有路由。

来源:GitHub Issue #48090、#73939

解决方法:

// app/@modal/[...catchAll]/page.tsx
export default function CatchAll() {
  return null;
}

// OR use catch-all in default.tsx
// app/@modal/default.tsx
export default function ModalDefault({ params }: { params: { catchAll?: string[] } }) {
  return null; // Handles all unmatched routes
}

参见模板:templates/parallel-routes-with-default.tsx


React 19.2 功能

Next.js 16 集成了 React 19.2,其中包含来自 React Canary 的新功能。

1. 视图过渡

使用场景:在页面切换之间实现平滑动画。

'use client'

import { useRouter } from 'next/navigation'
import { startTransition } from 'react'

export function NavigationLink({ href, children }: { href: string; children: React.ReactNode }) {
  const router = useRouter()

  function handleClick(e: React.MouseEvent) {
    e.preventDefault()

    // Wrap navigation in startTransition for View Transitions
    startTransition(() => {
      router.push(href)
    })
  }

  return <a href={href} onClick={handleClick}>{children}</a>
}

配合 CSS View Transitions API 使用:

/* app/globals.css */
@view-transition {
  navigation: auto;
}

/* Animate elements with view-transition-name */
.page-title {
  view-transition-name: page-title;
}

参见模板:templates/view-transitions-react-19.tsx


2. useEffectEvent()(实验性)

使用场景:从 useEffect 中提取非响应式逻辑。

'use client'

import { useEffect, experimental_useEffectEvent as useEffectEvent } from 'react'

export function ChatRoom({ roomId }: { roomId: string }) {
  const onConnected = useEffectEvent(() => {
    console.log('Connected to room:', roomId)
  })

  useEffect(() => {
    const connection = connectToRoom(roomId)
    onConnected() // Non-reactive callback

    return () => connection.disconnect()
  }, [roomId]) // Only re-run when roomId changes

  return <div>Chat Room {roomId}</div>
}

使用原因:当回调依赖项发生变化时,可避免不必要地重新运行 useEffect。


3. React Compiler(稳定版)

使用场景:无需使用 useMemo、useCallback 即可自动记忆化。

在 next.config.ts 中启用:

import type { NextConfig } from 'next'

const config: NextConfig = {
  experimental: {
    reactCompiler: true,
  },
}

export default config

安装插件:

npm install babel-plugin-react-compiler

示例(无需手动记忆化):

'use client'

export function ExpensiveList({ items }: { items: string[] }) {
  // React Compiler automatically memoizes this
  const filteredItems = items.filter(item => item.length > 3)

  return (
    <ul>
      {filteredItems.map(item => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  )
}

参见参考文档:references/react-19-integration.md


Turbopack(在 Next.js 16 中已稳定)

新增:Turbopack 现已成为 Next.js 16 中的默认打包器。

性能提升:

  • 生产构建速度提升 2–5 倍
  • Fast Refresh 速度最高提升 10 倍

退出使用(如有需要):

npm run build -- --webpack

启用文件系统缓存(实验性):

// next.config.ts
import type { NextConfig } from 'next'

const config: NextConfig = {
  experimental: {
    turbopack: {
      fileSystemCaching: true, // Beta: Persist cache between runs
    },
  },
}

export default config

Turbopack 的生产环境限制(截至 Next.js 16.1)

已知问题:

1. 与 Prisma 不兼容

来源:GitHub 讨论 #77721

使用 Prisma ORM(v6.5.0+)时,Turbopack 生产构建会失败。错误:“'path' 参数必须是字符串类型。”

解决方法:

# Use webpack for production builds
npm run build -- --webpack

或在 next.config.ts 中:

const config: NextConfig = {
  experimental: {
    turbo: false, // Disable Turbopack for production
  },
};

2. Source Maps 安全风险

来源:GitHub 讨论 #77721

Turbopack 目前始终会为浏览器构建生产环境 Source Maps,导致生产部署中源代码暴露。

解决方法:

// next.config.ts
const config: NextConfig = {
  productionBrowserSourceMaps: false, // Disable source maps
};

或在部署时排除 .map 文件:

# .vercelignore or similar
*.map

3. 外部模块哈希不匹配(Monorepo)

来源:GitHub Issue #87737

当 node_modules 结构不同时(pnpm、yarn workspaces、monorepo),Turbopack 生成的外部模块引用哈希会不匹配。这会导致生产构建中出现“找不到模块”错误。

症状:

  • 本地构建成功,但在 CI/CD 中失败
  • 打包后的引用与实际模块文件之间存在哈希不匹配

解决方法:

// next.config.ts
const config: NextConfig = {
  experimental: {
    serverExternalPackages: ['package-name'], // Explicitly externalize packages
  },
};

4. 打包体积差异(来自社区)

来源:GitHub 讨论 #77721

使用 Turbopack 构建的打包体积可能与使用 webpack 构建的不同。这是预期情况,Turbopack 正在不断成熟和优化。


常见错误与解决方法

1. 错误:params 是一个 Promise

错误:

Type 'Promise<{ id: string }>' is not assignable to type '{ id: string }'

原因:Next.js 16 将 params 改为异步。

解决方法:等待 params:

// ❌ Before
export default function Page({ params }: { params: { id: string } }) {
  const id = params.id
}

// ✅ After
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
}

2. 错误:searchParams 是一个 Promise

错误:

Property 'query' does not exist on type 'Promise<{ query: string }>'

原因:在 Next.js 16 中,searchParams 现在是异步的。

解决方法:

// ❌ Before
export default function Page({ searchParams }: { searchParams: { query: string } }) {
  const query = searchParams.query
}

// ✅ After
export default async function Page({ searchParams }: { searchParams: Promise<{ query: string }> }) {
  const { query } = await searchParams
}

3. 错误:cookies() 需要使用 await

错误:

'cookies' implicitly has return type 'any'

原因:在 Next.js 16 中,cookies() 现在是异步的。

解决方法:

// ❌ Before
import { cookies } from 'next/headers'

export function MyComponent() {
  const cookieStore = cookies()
}

// ✅ After
import { cookies } from 'next/headers'

export async function MyComponent() {
  const cookieStore = await cookies()
}

4. 错误:并行路由缺少 default.js

错误:

Error: Parallel route @modal/login was matched but no default.js was found

原因:Next.js 16 要求所有并行路由都包含 default.js。

解决方法:添加 default.tsx 文件:

// app/@modal/default.tsx
export default function ModalDefault() {
  return null
}

5. 错误:revalidateTag() 需要 2 个参数

错误:

Expected 2 arguments, but got 1

原因:在 Next.js 16 中,revalidateTag() 现在需要一个 cacheLife 参数。

解决方法:

// ❌ Before
revalidateTag('posts')

// ✅ After
revalidateTag('posts', 'max')

6. 错误:无法在 Server Component 中使用 React Hooks

错误:

You're importing a component that needs useState. It only works in a Client Component

原因:在 Server Component 中使用了 React Hooks。

解决方法:添加 'use client' 指令:

// ✅ Add 'use client' at the top
'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

7. 错误:middleware.ts 已弃用

警告:

Warning: middleware.ts is deprecated. Use proxy.ts instead.

解决方法:迁移到 proxy.ts:

// Rename: middleware.ts → proxy.ts
// Rename function: middleware → proxy

export function proxy(request: NextRequest) {
  // Same logic
}

8. 错误:Turbopack 构建失败

错误:

Error: Failed to compile with Turbopack

原因:Turbopack 现已成为 Next.js 16 的默认打包器。

解决方法:如果不兼容,请退出使用 Turbopack:

npm run build -- --webpack

9. 错误:next/image 的 src 无效

错误:

Invalid src prop (https://example.com/image.jpg) on `next/image`. Hostname "example.com" is not configured under images in your `next.config.js`

解决方法:在 next.config.ts 中添加远程模式:

const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
      },
    ],
  },
}

10. 错误:无法将 Server Component 导入 Client Component

错误:

You're importing a Server Component into a Client Component

解决方法:将 Server Component 作为子项传入:

// ❌ Wrong
'use client'
import { ServerComponent } from './server-component' // Error

export function ClientComponent() {
  return <ServerComponent />
}

// ✅ Correct
'use client'

export function ClientComponent({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>
}

// Usage
<ClientComponent>
  <ServerComponent /> {/* Pass as children */}
</ClientComponent>

11. 错误:generateStaticParams 不起作用

原因:generateStaticParams 仅适用于静态生成(export const dynamic = 'force-static')。

解决方法:

export const dynamic = 'force-static'

export async function generateStaticParams() {
  const posts = await fetch('/api/posts').then(r => r.json())
  return posts.map((post: { id: string }) => ({ id: post.id }))
}

12. 错误:fetch() 未缓存

原因:Next.js 16 使用通过 "use cache" 指令选择性启用的缓存。

解决方法:在组件或函数中添加 "use cache":

'use cache'

export async function getPosts() {
  const response = await fetch('/api/posts')
  return response.json()
}

13. 错误:路由组发生路由冲突

错误:

Error: Conflicting routes: /about and /(marketing)/about

原因:路由组生成了相同的 URL 路径。

解决方案:确保路由组之间没有冲突:

app/
├── (marketing)/about/page.tsx  → /about
└── (shop)/about/page.tsx       → ERROR: Duplicate /about

# Fix: Use different routes
app/
├── (marketing)/about/page.tsx     → /about
└── (shop)/store-info/page.tsx     → /store-info

14. 错误:元数据未更新

原因:使用动态元数据,却没有使用 generateMetadata()。

解决方案:对动态页面使用 generateMetadata():

export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
  const { id } = await params
  const post = await fetch(`/api/posts/${id}`).then(r => r.json())

  return {
    title: post.title,
    description: post.excerpt,
  }
}

15. 错误:next/font 字体未加载

原因:字体变量未应用到 HTML 元素。

解决方案:将字体变量应用到 <html> 或 <body>:

import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html className={inter.variable}> {/* ✅ Apply variable */}
      <body>{children}</body>
    </html>
  )
}

16. 错误:浏览器中无法使用环境变量

原因:仅限服务端使用的环境变量不会暴露给浏览器。

解决方案:添加前缀 NEXT_PUBLIC_,以便在客户端访问:

# .env
SECRET_KEY=abc123                  # Server-only
NEXT_PUBLIC_API_URL=https://api    # Available in browser
// Server Component (both work)
const secret = process.env.SECRET_KEY
const apiUrl = process.env.NEXT_PUBLIC_API_URL

// Client Component (only public vars work)
const apiUrl = process.env.NEXT_PUBLIC_API_URL

17. 错误:未找到 Server Action

错误:

Error: Could not find Server Action

原因:缺少 'use server' 指令。

解决方案:添加 'use server':

// ❌ Before
export async function createPost(formData: FormData) {
  await db.posts.create({ ... })
}

// ✅ After
'use server'

export async function createPost(formData: FormData) {
  await db.posts.create({ ... })
}

18. 错误:TypeScript 路径别名不起作用

原因:tsconfig.json 中的 baseUrl 或 paths 配置不正确。

解决方案:正确配置:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"],
      "@/components/*": ["./app/components/*"]
    }
  }
}

参见参考资料:references/top-errors.md


19. 错误:多次重定向导致客户端导航受限

错误:Throttling navigation to prevent the browser from hanging 来源:GitHub Issue #87245

原因:当 proxy.ts(或 middleware.ts)执行重定向以添加查询参数,并且 Server Component 也调用 redirect() 添加其他查询参数时,在生产构建中,通过 <Link> 进行客户端导航会失败。这是从 Next.js 14 到 16 的回归问题。

症状:

  • 在 next dev(开发模式)下正常
  • 直接访问 URL(完整页面加载)时正常
  • 在生产构建中通过 <Link> 进行客户端导航时失败
  • 预取会导致无限重定向循环

解决方案:对导航到包含重定向逻辑页面的链接禁用预取:

// ✅ Workaround: Disable prefetch
<Link href="/my-route" prefetch={false}>
  Navigate
</Link>

20. 错误:Cache Components 与 i18n 动态片段不兼容

错误:即使使用了 generateStaticParams,路由仍会变为动态路由 来源:GitHub Issue #86870

原因:使用 intlayer、next-intl 或 lingui 等国际化(i18n)框架时,Cache Components("use cache" 指令)无法用于动态片段。即使在布局级别使用了 generateStaticParams,访问 params 也会使路由变为动态路由。

原因说明:每个 i18n 框架都需要访问 params 来获取区域设置。在 Next.js 16 中,访问 params 是异步调用,这会使整个页面退出缓存。

解决方案:在每个动态片段级别都添加 generateStaticParams:

// app/[locale]/[id]/page.tsx
export async function generateStaticParams() {
  return [
    { locale: 'en', id: '1' },
    { locale: 'en', id: '2' },
    // ... all combinations
  ];
}

'use cache'

export default async function Page({ params }: Props) {
  // Now caching works
}

补充说明:[locale] 动态片段在编译期间会收到 _next 等无效值,在初始化 i18n 提供程序时导致 RangeError: Incorrect locale information provided。


21. 错误:在 Server Components 中,instanceof 对自定义错误类失效

错误:即使错误属于 CustomError,instanceof CustomError 也会返回 false 来源:GitHub Issue #87614

原因:Server Components 中的模块重复会导致自定义错误类被加载两次,从而产生不同的原型。

解决方案:使用 error.name 或 error.constructor.name,而不是 instanceof:

// ❌ Wrong: instanceof doesn't work
try {
  throw new CustomError('Test error');
} catch (error) {
  if (error instanceof CustomError) { // ❌ false
    // Never reached
  }
}

// ✅ Correct: Use error.name
try {
  throw new CustomError('Test error');
} catch (error) {
  if (error instanceof Error && error.name === 'CustomError') { // ✅ true
    // Handle CustomError
  }
}

// ✅ Alternative: Use constructor.name
if (error.constructor.name === 'CustomError') {
  // Handle CustomError
}

22. 错误:TypeScript 无法捕获传递给客户端组件的不可序列化属性

错误:向客户端组件传递函数/类实例时发生运行时错误 来源:GitHub Issue #86748

原因:Next.js TypeScript 插件无法捕获从服务端组件传递给客户端组件的不可序列化属性。这会导致编译时无法检测到的运行时错误。

发生原因:只有可序列化的数据(与 JSON 兼容)才能跨越服务端/客户端边界。函数、类实例和 Symbol 均无法序列化。

解决方案:仅传递可序列化的属性:

// ❌ Wrong: Function not serializable
const user = {
  name: 'John',
  getProfile: () => console.log('profile'), // ❌ Not serializable
};
<ClientComponent user={user} />

// ✅ Correct: Only serializable props
interface SerializableUser {
  name: string;
  email: string;
  // No functions, no class instances, no Symbols
}

// ✅ Alternative: Create functions in Client Component
'use client';

export default function ClientComponent({ user }: { user: { name: string } }) {
  const getProfile = () => console.log('profile'); // Define in client
  return <div onClick={getProfile}>{user.name}</div>;
}

运行时验证:

import { z } from 'zod';

const UserSchema = z.object({
  name: z.string(),
  email: z.string(),
});

type User = z.infer<typeof UserSchema>;

23. 错误:Turbopack 使用 Prisma 时生产构建失败

错误:The 'path' argument must be of type string 来源:GitHub Discussion #77721

原因:使用 Prisma ORM(v6.5.0+)时,Turbopack 生产构建会失败。

解决方案:生产构建使用 webpack:

npm run build -- --webpack

或者在配置中禁用 Turbopack:

// next.config.ts
const config: NextConfig = {
  experimental: {
    turbo: false,
  },
};

24. 错误:Turbopack 通过源映射暴露源代码

错误:生产构建中可见源代码 来源:GitHub Discussion #77721

原因:Turbopack 始终为浏览器构建生产源映射,从而暴露源代码。

解决方案:禁用生产源映射:

// next.config.ts
const config: NextConfig = {
  productionBrowserSourceMaps: false,
};

或者在部署时排除 .map 文件:

# .vercelignore
*.map

25. 错误:生产环境中找不到模块(Turbopack monorepo)

错误:尽管本地构建成功,生产环境中仍出现 Module not found 来源:GitHub Issue #87737

原因:当 node_modules 结构不同时(pnpm、yarn workspaces、monorepos),Turbopack 生成的外部模块引用所用哈希值会与实际模块文件不匹配。

症状:

  • 本地构建成功,但在 CI/CD 中失败
  • 捆绑引用中的哈希值与实际模块文件不匹配

解决方案:显式将软件包设为外部依赖:

// next.config.ts
const config: NextConfig = {
  experimental: {
    serverExternalPackages: ['package-name'],
  },
};

参阅参考资料:references/top-errors.md


模板与资源

Next.js 16 专用模板(位于 templates/):

  • app-router-async-params.tsx - 异步参数迁移模式
  • parallel-routes-with-default.tsx - 必需的 default.js 文件
  • cache-component-use-cache.tsx - 使用 "use cache" 的 Cache Components
  • revalidate-tag-cache-life.ts - 更新后的 revalidateTag() 与 cacheLife
  • server-action-update-tag.ts - 用于读后写的 updateTag()
  • proxy-migration.ts - 从 middleware.ts 迁移到 proxy.ts
  • view-transitions-react-19.tsx - React 19.2 View Transitions
  • next.config.ts - Next.js 16 配置

随附参考资料(位于 references/):

  • next-16-migration-guide.md - 完整的 Next.js 15→16 迁移指南
  • cache-components-guide.md - Cache Components 深入指南
  • proxy-vs-middleware.md - Proxy.ts 与 middleware.ts
  • async-route-params.md - 异步参数破坏性变更详情
  • react-19-integration.md - Next.js 16 中的 React 19.2 功能
  • top-errors.md - 18+ 个常见错误及解决方案

外部文档:


版本兼容性

软件包最低版本推荐版本
Next.js16.0.016.1.1+
React19.2.019.2.3+
Node.js20.9.020.9.0+
TypeScript5.1.05.7.0+
Turbopack(内置)稳定版

检查版本:

./scripts/check-versions.sh

Token 效率

预计 Token 节省:65-70%

不使用技能(根据文档手动设置):

  • 阅读 Next.js 16 迁移指南:约 5k 个 Token
  • 阅读 App Router 文档:约 8k 个 Token
  • 阅读 Server Actions 文档:约 4k 个 Token
  • 阅读 Metadata API 文档:约 3k 个 Token
  • 反复试错修复:约 8k 个 Token
  • 总计:约 28k 个 Token

使用技能:

  • 加载技能:约 8k 个 Token
  • 使用模板:约 2k 个 Token
  • 总计:约 10k 个 Token
  • 节省:约 18k 个 Token(约 64%)

预防的错误:已记录的 25 个错误 = 100% 错误预防


维护

上次验证时间:2026-01-21 技能版本:3.1.0 变更:新增 7 个错误(导航节流、i18n 缓存、Turbopack 限制、instanceof 失效、不可序列化的 props)。扩展了异步 params codemod 的限制、缓存默认值和并行路由边缘情况。

下次审查:2026-04-21(每季度) 维护者:Jezweb | [email protected] 代码仓库:https://github.com/jezweb/claude-skills

更新触发条件:

  • Next.js 主要/次要版本发布
  • React 主要版本发布
  • API 中的破坏性变更
  • 新的 Turbopack 功能

版本检查:

cd skills/nextjs
./scripts/check-versions.sh

SKILL.md 结束

评论

支持 Markdown