nextjs by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill nextjs版本 : Next.js 16.1.1 React 版本 : 19.2.3 Node.js : 20.9+ 最后验证 : 2026-01-09
重点 : Next.js 16 的破坏性变更和知识缺口 (2024年12月+)。
在以下情况下使用此技能:
"use cache" 指令的缓存组件 (Next.js 16 新增)revalidateTag()、updateTag()、 (Next.js 16 更新)广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
refresh()params、searchParams、cookies()、headers() 现在为异步)useEffectEvent()、React 编译器)以下情况请勿使用此技能:
cloudflare-nextjs 技能clerk-auth、better-auth 或其他特定于身份验证的技能cloudflare-d1、drizzle-orm-d1 或特定于数据库的技能tailwind-v4-shadcn 技能处理 Tailwind + shadcn/uizustand-state-management、tanstack-query 技能react-hook-form-zod 技能与其他技能的关系 :
严重 : 2025年12月披露了三个影响 Next.js 与 React Server Components 的安全漏洞:
| CVE | 严重程度 | 受影响版本 | 描述 |
|---|---|---|---|
| CVE-2025-66478 | 严重 (10.0) | 15.x, 16.x | Server Component 任意代码执行 |
| CVE-2025-55184 | 高 | 13.x-16.x | 通过畸形请求导致拒绝服务 |
| CVE-2025-55183 | 中 | 13.x-16.x | 错误响应中的源代码暴露 |
必需操作 : 立即升级到 Next.js 16.1.1 或更高版本。
npm update next
# 验证: npm list next 应显示 16.1.1+
参考 :
16.1 版本新增内容 :
next dev --inspect 支持重要 : Next.js 16 引入了多个破坏性变更。如果要从 Next.js 15 或更早版本迁移,请仔细阅读本节。
破坏性变更 : params、searchParams、cookies()、headers()、draftMode() 现在为异步,必须使用 await。
之前 (Next.js 15) :
// ❌ 这在 Next.js 16 中不再有效
export default function Page({ params, searchParams }: {
params: { slug: string }
searchParams: { query: string }
}) {
const slug = params.slug // ❌ 错误: params 是一个 Promise
const query = searchParams.query // ❌ 错误: searchParams 是一个 Promise
return <div>{slug}</div>
}
之后 (Next.js 16) :
// ✅ 正确: await params 和 searchParams
export default async function Page({ params, searchParams }: {
params: Promise<{ slug: string }>
searchParams: Promise<{ query: string }>
}) {
const { slug } = await params // ✅ 等待 Promise
const { query } = await searchParams // ✅ 等待 Promise
return <div>{slug}</div>
}
适用于 :
paramssearchParamsnext/headers 的 cookies()next/headers 的 headers()next/headers 的 draftMode()迁移 :
// ❌ 之前
import { cookies, headers } from 'next/headers'
export function MyComponent() {
const cookieStore = cookies() // ❌ 同步访问
const headersList = headers() // ❌ 同步访问
}
// ✅ 之后
import { cookies, headers } from 'next/headers'
export async function MyComponent() {
const cookieStore = await cookies() // ✅ 异步访问
const headersList = await headers() // ✅ 异步访问
}
代码修改工具 : 运行 npx @next/codemod@canary upgrade latest 以自动迁移。
代码修改工具限制 (社区来源): 官方代码修改工具处理约 80% 的异步 API 迁移,但会遗漏边缘情况:
运行代码修改工具后,搜索 @next-codemod-error 注释,标记其无法自动修复的位置。
客户端组件的手动迁移 :
// 对于客户端组件,使用 React.use() 来解包 Promise
'use client';
import { use } from 'react';
export default function ClientComponent({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = use(params); // 在客户端解包 Promise
return <div>{id}</div>;
}
参见模板 : templates/app-router-async-params.tsx
破坏性变更 : middleware.ts 在 Next.js 16 中已弃用。请改用 proxy.ts。
变更原因 : proxy.ts 通过在 Node.js 运行时运行(而非 Edge 运行时)使网络边界变得明确。这提供了更好的清晰度,区分了边缘中间件和服务器端代理。
迁移步骤 :
middleware.ts → proxy.tsmiddleware → proxymatcher → config.matcher (语法相同)之前 (Next.js 15) :
// middleware.ts ❌ 在 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 ✅ 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
default.js (破坏性变更)破坏性变更 : 并行路由现在要求显式的 default.js 文件。没有它们,路由将在软导航期间失败。
结构 :
app/
├── @auth/
│ ├── login/
│ │ └── page.tsx
│ └── default.tsx ← Next.js 16 必需
├── @dashboard/
│ ├── overview/
│ │ └── page.tsx
│ └── default.tsx ← 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 // 或 <Skeleton /> 或重定向
}
// app/@dashboard/default.tsx
export default function DashboardDefault() {
return null
}
为何必需 : Next.js 16 改变了并行路由处理软导航的方式。没有 default.js,未匹配的插槽将在客户端导航期间出错。
参见模板 : templates/parallel-routes-with-default.tsx
以下功能在 Next.js 16 中已移除 :
next lint 命令 - 直接使用 ESLint 或 Biome。serverRuntimeConfig 和 publicRuntimeConfig - 改用环境变量。experimental.ppr 标志 - 演变为缓存组件。使用 "use cache" 指令。scroll-behavior: smooth - 如果需要,请手动添加。迁移 :
npx eslint . 或 npx biome lint .。process.env.VARIABLE 替换 serverRuntimeConfig。experimental.ppr 迁移到 "use cache" 指令 (参见缓存组件部分)。Next.js 16 要求 :
检查版本 :
node --version # 应为 20.9+
npm --version # 应为 10+
npx next --version # 应为 16.0.0+
升级 Node.js :
# 使用 nvm
nvm install 20
nvm use 20
nvm alias default 20
# 使用 Homebrew (macOS)
brew install node@20
# 使用 apt (Ubuntu/Debian)
sudo apt update
sudo apt install nodejs npm
Next.js 16 更改了 next/image 的默认值:
| 设置 | Next.js 15 | Next.js 16 |
|---|---|---|
| TTL (缓存持续时间) | 60 秒 | 4 小时 |
| imageSizes | [16, 32, 48, 64, 96, 128, 256, 384] | [640, 750, 828, 1080, 1200] (减少) |
| qualities | [75, 90, 100] | [75] (单一质量) |
影响 :
覆盖默认值 (如果需要):
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
images: {
minimumCacheTTL: 60, // 恢复为 60 秒
deviceSizes: [640, 750, 828, 1080, 1200, 1920], // 添加更大尺寸
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // 恢复旧尺寸
formats: ['image/webp'], // 默认
},
}
export default config
参见模板 : templates/image-optimization.tsx
Next.js 16 新增 : 缓存组件通过 "use cache" 指令引入了选择性加入缓存,取代了 Next.js 15 中的隐式缓存。
变更内容 :
"use cache" 指令的选择性加入缓存变更原因 : 显式缓存让开发者拥有更多控制权,并使缓存行为可预测。
重要缓存默认值 (社区来源):
| 功能 | Next.js 14 | Next.js 15/16 |
|---|---|---|
| fetch() 请求 | 默认缓存 | 默认不缓存 |
| 路由器缓存 (动态页面) | 在客户端缓存 | 默认不缓存 |
| 路由器缓存 (静态页面) | 缓存 | 仍然缓存 |
| 路由处理器 (GET) | 缓存 | 默认动态 |
最佳实践 : 在 Next.js 16 中默认为动态。从不缓存开始,在有益的地方添加缓存,而不是调试意外的缓存命中。始终使用生产构建进行测试 - 开发服务器的行为不同。
缓存组件支持 :
"use cache" 指令语法 : 在 Server Component、函数或路由处理器的顶部添加 "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()
}
// 在组件中使用
import { getExpensiveData } from '@/lib/data'
export async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const product = await getExpensiveData(id) // 已缓存
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
PPR 允许缓存页面的静态部分,同时按需渲染动态部分。
模式 :
// app/dashboard/page.tsx
// 静态头部 (缓存)
'use cache'
async function StaticHeader() {
return <header>My App</header>
}
// 动态用户信息 (不缓存)
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>
}
// 页面组合两者
export default function Dashboard() {
return (
<div>
<StaticHeader /> {/* 缓存 */}
<DynamicUserInfo /> {/* 动态 */}
</div>
)
}
何时使用 PPR :
参见参考 : references/cache-components-guide.md
revalidateTag() - 更新后的 API破坏性变更 : revalidateTag() 现在需要第二个参数 (cacheLife 配置文件) 以实现过时期间重新验证行为。
之前 (Next.js 15) :
import { revalidateTag } from 'next/cache'
export async function updatePost(id: string) {
await fetch(`/api/posts/${id}`, { method: 'PATCH' })
revalidateTag('posts') // ❌ 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') // ✅ Next.js 16 中需要第二个参数
}
内置缓存生命周期配置文件 :
'max' - 最大过时时间 (推荐用于大多数用例)'hours' - 数小时后过时'days' - 数天后过时'weeks' - 数周后过时'default' - 默认缓存行为自定义缓存生命周期配置文件 :
revalidateTag('posts', {
stale: 3600, // 1 小时后过时 (秒)
revalidate: 86400, // 每 24 小时重新验证 (秒)
expire: false, // 永不过期 (可选)
})
服务器操作中的模式 :
'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') // ✅ 使用最大过时时间重新验证
}
参见模板 : templates/revalidate-tag-cache-life.ts
updateTag() - 新增 API (仅限服务器操作)Next.js 16 新增 : updateTag() 为服务器操作提供读取你所写内容的语义。
功能 :
与 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
// 更新数据库
await db.users.update({ name, email })
// 立即刷新缓存 (读取你所写内容)
updateTag('user-profile')
// 用户立即看到更新后的数据 (无过时数据)
}
何时使用 :
updateTag() : 用户设置、个人资料更新、关键变更 (立即反馈)revalidateTag() : 博客文章、产品列表、非关键更新 (后台重新验证)参见模板 : templates/server-action-update-tag.ts
refresh() - 新增 API (仅限服务器操作)Next.js 16 新增 : refresh() 仅刷新未缓存的数据 (补充客户端的 router.refresh())。
何时使用 :
router.refresh()模式 :
'use server'
import { refresh } from 'next/cache'
export async function refreshDashboard() {
// 刷新未缓存的数据 (例如,实时指标)
refresh()
// 缓存数据 (例如,静态头部) 保持缓存
}
与 revalidateTag() 和 updateTag() 的区别:
refresh(): 仅刷新未缓存的数据revalidateTag(): 重新验证特定标记的数据 (过时期间重新验证)updateTag(): 立即使特定标记的数据过期并刷新参见参考 : references/cache-components-guide.md
重要 : params 和 headers() 在 Next.js 16 的路由处理器中现在为异步。
示例 :
// 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 // ✅ 在 Next.js 16 中 await params
const headersList = await headers() // ✅ 在 Next.js 16 中 await headers
const post = await db.posts.findUnique({ where: { id } })
return NextResponse.json(post)
}
参见模板 : templates/route-handler-api.ts
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) {
// 检查身份验证
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) {
// 检查身份验证
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
Next.js 16 中的破坏性变更 : 并行路由现在要求显式的 default.js 文件。
结构 :
app/
├── @modal/
│ ├── login/page.tsx
│ └── default.tsx ← Next.js 16 必需
├── @feed/
│ ├── trending/page.tsx
│ └── default.tsx ← Next.js 16 必需
└── layout.tsx
默认文件 (必需) :
// app/@modal/default.tsx
export default function ModalDefault() {
return null // 或 <Skeleton /> 或重定向
}
为何必需 : Next.js 16 改变了软导航的处理方式。没有 default.js,未匹配的插槽将在客户端导航期间出错。
高级边缘情况 (社区来源): 即使有 default.js 文件,硬导航或刷新带有并行路由的路由也可能返回 404 错误。解决方法是添加一个捕获所有路由。
来源 : GitHub Issue #48090, #73939
解决方法 :
// app/@modal/[...catchAll]/page.tsx
export default function CatchAll() {
return null;
}
// 或在 default.tsx 中使用捕获所有
// app/@modal/default.tsx
export default function ModalDefault({ params }: { params: { catchAll?: string[] } }) {
return null; // 处理所有未匹配的路由
}
参见模板 : templates/parallel-routes-with-default.tsx
Next.js 16 集成了 React 19.2,其中包含来自 React Canary 的新功能。
用例 : 页面过渡之间的平滑动画。
'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()
// 将导航包装在 startTransition 中以实现视图过渡
startTransition(() => {
router.push(href)
})
}
return <a href={href} onClick={handleClick}>{children}</a>
}
使用 CSS 视图过渡 API :
/* app/globals.css */
@view-transition {
navigation: auto;
}
/* 使用 view-transition-name 为元素添加动画 */
.page-title {
view-transition-name: page-title;
}
参见模板 : templates/view-transitions-react-19.tsx
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() // 非响应式回调
return () => connection.disconnect()
}, [roomId]) // 仅在 roomId 更改时重新运行
return <div>Chat Room {roomId}</div>
}
为何使用 : 防止回调依赖项更改时不必要的 useEffect 重新运行。
用例 : 无需 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 编译器自动记忆化此操作
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 中的默认打包工具。
性能改进 :
选择退出 (如果需要):
npm run build -- --webpack
启用文件系统缓存 (实验性):
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
experimental: {
turbopack: {
fileSystemCaching: true, // Beta: 在运行之间持久化缓存
},
},
}
export default config
已知问题 :
Turbopack 生产构建在使用 Prisma ORM (v6.5.0+) 时失败。错误: "The 'path' argument must be of type string."
解决方法 :
# 使用 webpack 进行生产构建
npm run build -- --webpack
或在 next.config.ts 中:
const config: NextConfig = {
experimental: {
turbo: false, // 为生产禁用 Turbopack
},
};
Turbopack 目前始终为浏览器构建生产源映射,在生产部署中暴露源代码。
解决方法 :
// next.config.ts
const config: NextConfig = {
productionBrowserSourceMaps: false, // 禁用源映射
};
或在部署中排除 .map 文件:
# .vercelignore 或类似文件
*.map
来源 : GitHub Issue #87737
当 node_modules 结构不同时 (pnpm、yarn workspaces、monorepos),Turbopack 生成的外部模块引用哈希不匹配。这导致生产构建中出现 "Module not found" 错误。
症状 :
解决方法 :
// next.config.ts
const config: NextConfig = {
experimental: {
serverExternalPackages: ['package-name'], // 显式外部化包
},
};
使用 Turbopack 构建的包大小可能与 webpack 构建不同。这是预期的,并且随着 Turbopack 的成熟正在优化。
params 是一个 Promise错误 :
Type 'Promise<{ id: string }>' is not
Version : Next.js 16.1.1 React Version : 19.2.3 Node.js : 20.9+ Last Verified : 2026-01-09
Focus : Next.js 16 breaking changes and knowledge gaps (December 2024+).
Use this skill when you need:
"use cache" directive (NEW in Next.js 16)revalidateTag(), updateTag(), refresh() (Updated in Next.js 16)params, searchParams, cookies(), headers() now async)useEffectEvent(), React Compiler)Do NOT use this skill for:
cloudflare-nextjs skill insteadclerk-auth, better-auth, or other auth-specific skillscloudflare-d1, drizzle-orm-d1, or database-specific skillstailwind-v4-shadcn skill for Tailwind + shadcn/uizustand-state-management, tanstack-query skillsRelationship with Other Skills :
CRITICAL : Three security vulnerabilities were disclosed in December 2025 affecting Next.js with React Server Components:
| CVE | Severity | Affected | Description |
|---|---|---|---|
| CVE-2025-66478 | CRITICAL (10.0) | 15.x, 16.x | Server Component arbitrary code execution |
| CVE-2025-55184 | HIGH | 13.x-16.x | Denial of Service via malformed request |
| CVE-2025-55183 | MEDIUM | 13.x-16.x | Source code exposure in error responses |
Action Required : Upgrade to Next.js 16.1.1 or later immediately.
npm update next
# Verify: npm list next should show 16.1.1+
References :
New in 16.1 :
next dev --inspect supportIMPORTANT : Next.js 16 introduces multiple breaking changes. Read this section carefully if migrating from Next.js 15 or earlier.
Breaking Change : params, searchParams, cookies(), headers(), draftMode() are now async and must be awaited.
Before (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>
}
After (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>
}
Applies to :
params in pages, layouts, route handlerssearchParams in pagescookies() from next/headersheaders() from next/headersdraftMode() from next/headersMigration :
// ❌ 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 : Run npx @next/codemod@canary upgrade latest to automatically migrate.
Codemod Limitations (Community-sourced): The official codemod handles ~80% of async API migrations but misses edge cases:
After running the codemod, search for @next-codemod-error comments marking places it couldn't auto-fix.
Manual Migration for Client Components :
// 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>;
}
See Template : templates/app-router-async-params.tsx
Breaking Change : middleware.ts is deprecated in Next.js 16. Use proxy.ts instead.
Why the Change : proxy.ts makes the network boundary explicit by running on Node.js runtime (not Edge runtime). This provides better clarity between edge middleware and server-side proxies.
Migration Steps :
middleware.ts → proxy.tsmiddleware → proxymatcher → config.matcher (same syntax)Before (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*',
}
After (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*',
}
Note : middleware.ts still works in Next.js 16 but is deprecated. Migrate to proxy.ts for future compatibility.
See Template : templates/proxy-migration.ts See Reference : references/proxy-vs-middleware.md
default.js (BREAKING)Breaking Change : Parallel routes now require explicit default.js files. Without them, routes will fail during soft navigation.
Structure :
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
Layout :
// 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>
)
}
Default Fallback (REQUIRED):
// app/@auth/default.tsx
export default function AuthDefault() {
return null // or <Skeleton /> or redirect
}
// app/@dashboard/default.tsx
export default function DashboardDefault() {
return null
}
Why Required : Next.js 16 changed how parallel routes handle soft navigation. Without default.js, unmatched slots will error during client-side navigation.
See Template : templates/parallel-routes-with-default.tsx
The following features are REMOVED in Next.js 16 :
next lint command - Use ESLint or Biome directly.serverRuntimeConfig and publicRuntimeConfig - Use environment variables instead.experimental.ppr flag - Evolved into Cache Components. Use "use cache" directive.scroll-behavior: smooth - Add manually if needed.Migration :
npx eslint . or npx biome lint . directly.serverRuntimeConfig with process.env.VARIABLE.experimental.ppr to "use cache" directive (see Cache Components section).Next.js 16 requires :
Check Versions :
node --version # Should be 20.9+
npm --version # Should be 10+
npx next --version # Should be 16.0.0+
Upgrade 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
Next.js 16 changednext/image defaults:
| Setting | Next.js 15 | Next.js 16 |
|---|---|---|
| TTL (cache duration) | 60 seconds | 4 hours |
| imageSizes | [16, 32, 48, 64, 96, 128, 256, 384] | [640, 750, 828, 1080, 1200] (reduced) |
| qualities | [75, 90, 100] | [75] (single quality) |
Impact :
Override Defaults (if needed):
// 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
See Template : templates/image-optimization.tsx
NEW in Next.js 16 : Cache Components introduce opt-in caching with the "use cache" directive, replacing implicit caching from Next.js 15.
What Changed :
"use cache" directiveWhy the Change : Explicit caching gives developers more control and makes caching behavior predictable.
Important Caching Defaults (Community-sourced):
| Feature | Next.js 14 | Next.js 15/16 |
|---|---|---|
| fetch() requests | Cached by default | NOT cached by default |
| Router Cache (dynamic pages) | Cached on client | NOT cached by default |
| Router Cache (static pages) | Cached | Still cached |
| Route Handlers (GET) | Cached | Dynamic by default |
Best Practice : Default to dynamic in Next.js 16. Start with no caching and add it where beneficial, rather than debugging unexpected cache hits. Always test with production builds - the development server behaves differently.
Cache Components enable :
"use cache" DirectiveSyntax : Add "use cache" at the top of a Server Component, function, or route handler.
Component-level caching :
// 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>
)
}
Function-level caching :
// 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>
}
Page-level caching :
// 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>
)
}
See Template : templates/cache-component-use-cache.tsx
PPR allows caching static parts of a page while rendering dynamic parts on-demand.
Pattern :
// 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>
)
}
When to Use PPR :
See Reference : references/cache-components-guide.md
revalidateTag() - Updated APIBREAKING CHANGE : revalidateTag() now requires a second argument (cacheLife profile) for stale-while-revalidate behavior.
Before (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
}
After (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
}
Built-in Cache Life Profiles :
'max' - Maximum staleness (recommended for most use cases)'hours' - Stale after hours'days' - Stale after days'weeks' - Stale after weeks'default' - Default cache behaviorCustom Cache Life Profile :
revalidateTag('posts', {
stale: 3600, // Stale after 1 hour (seconds)
revalidate: 86400, // Revalidate every 24 hours (seconds)
expire: false, // Never expire (optional)
})
Pattern in Server Actions :
'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
}
See Template : templates/revalidate-tag-cache-life.ts
updateTag() - NEW API (Server Actions Only)NEW in Next.js 16 : updateTag() provides read-your-writes semantics for Server Actions.
What it does :
Difference fromrevalidateTag():
revalidateTag(): Stale-while-revalidate (shows stale data, revalidates in background)updateTag(): Immediate refresh (expires cache, fetches fresh data in same request)Use Case : Forms, user settings, or any mutation where user expects immediate feedback.
Pattern :
'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)
}
When to Use :
updateTag() : User settings, profile updates, critical mutations (immediate feedback)revalidateTag() : Blog posts, product listings, non-critical updates (background revalidation)See Template : templates/server-action-update-tag.ts
refresh() - NEW API (Server Actions Only)NEW in Next.js 16 : refresh() refreshes uncached data only (complements client-side router.refresh()).
When to Use :
router.refresh() on server sidePattern :
'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
}
Difference fromrevalidateTag() and updateTag():
refresh(): Only refreshes uncached datarevalidateTag(): Revalidates specific tagged data (stale-while-revalidate)updateTag(): Immediately expires and refreshes specific tagged dataSee Reference : references/cache-components-guide.md
IMPORTANT : params and headers() are now async in Next.js 16 route handlers.
Example :
// 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)
}
See Template : templates/route-handler-api.ts
Next.js 16 introducesproxy.ts to replace middleware.ts.
middleware.ts : Runs on Edge runtime (limited Node.js APIs)proxy.ts : Runs on Node.js runtime (full Node.js APIs)The new proxy.ts makes the network boundary explicit and provides more flexibility.
Before (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*',
}
After (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*',
}
See Template : templates/proxy-migration.ts See Reference : references/proxy-vs-middleware.md
Breaking Change in Next.js 16 : Parallel routes now require explicit default.js files.
Structure :
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
Default Files (REQUIRED) :
// app/@modal/default.tsx
export default function ModalDefault() {
return null // or <Skeleton /> or redirect
}
Why Required : Next.js 16 changed soft navigation handling. Without default.js, unmatched slots error during client-side navigation.
Advanced Edge Case (Community-sourced): Even WITH default.js files, hard navigating or refreshing routes with parallel routes can return 404 errors. The workaround is adding a catch-all route.
Source : GitHub Issue #48090, #73939
Workaround :
// 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
}
See Template : templates/parallel-routes-with-default.tsx
Next.js 16 integrates React 19.2, which includes new features from React Canary.
Use Case : Smooth animations between page transitions.
'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>
}
With CSS View Transitions API :
/* app/globals.css */
@view-transition {
navigation: auto;
}
/* Animate elements with view-transition-name */
.page-title {
view-transition-name: page-title;
}
See Template : templates/view-transitions-react-19.tsx
useEffectEvent() (Experimental)Use Case : Extract non-reactive logic from 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>
}
Why Use It : Prevents unnecessary useEffect re-runs when callback dependencies change.
Use Case : Automatic memoization without useMemo, useCallback.
Enable in next.config.ts :
import type { NextConfig } from 'next'
const config: NextConfig = {
experimental: {
reactCompiler: true,
},
}
export default config
Install Plugin :
npm install babel-plugin-react-compiler
Example (no manual memoization needed):
'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>
)
}
See Reference : references/react-19-integration.md
NEW : Turbopack is now the default bundler in Next.js 16.
Performance Improvements :
Opt-out (if needed):
npm run build -- --webpack
Enable File System Caching (experimental):
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
experimental: {
turbopack: {
fileSystemCaching: true, // Beta: Persist cache between runs
},
},
}
export default config
Known Issues :
Source : GitHub Discussion #77721
Turbopack production builds fail with Prisma ORM (v6.5.0+). Error: "The 'path' argument must be of type string."
Workaround :
# Use webpack for production builds
npm run build -- --webpack
Or in next.config.ts:
const config: NextConfig = {
experimental: {
turbo: false, // Disable Turbopack for production
},
};
Source : GitHub Discussion #77721
Turbopack currently always builds production source maps for the browser , exposing source code in production deployments.
Workaround :
// next.config.ts
const config: NextConfig = {
productionBrowserSourceMaps: false, // Disable source maps
};
Or exclude .map files in deployment:
# .vercelignore or similar
*.map
Source : GitHub Issue #87737
Turbopack generates external module references with hashes that don't match when node_modules structure differs (pnpm, yarn workspaces, monorepos). This causes "Module not found" errors in production builds.
Symptoms :
Workaround :
// next.config.ts
const config: NextConfig = {
experimental: {
serverExternalPackages: ['package-name'], // Explicitly externalize packages
},
};
Source : GitHub Discussion #77721
Bundle sizes built with Turbopack may differ from webpack builds. This is expected and being optimized as Turbopack matures.
params is a PromiseError :
Type 'Promise<{ id: string }>' is not assignable to type '{ id: string }'
Cause : Next.js 16 changed params to async.
Solution : Await 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
}
searchParams is a PromiseError :
Property 'query' does not exist on type 'Promise<{ query: string }>'
Cause : searchParams is now async in Next.js 16.
Solution :
// ❌ 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
}
cookies() requires awaitError :
'cookies' implicitly has return type 'any'
Cause : cookies() is now async in Next.js 16.
Solution :
// ❌ 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()
}
default.jsError :
Error: Parallel route @modal/login was matched but no default.js was found
Cause : Next.js 16 requires default.js for all parallel routes.
Solution : Add default.tsx files:
// app/@modal/default.tsx
export default function ModalDefault() {
return null
}
revalidateTag() requires 2 argumentsError :
Expected 2 arguments, but got 1
Cause : revalidateTag() now requires a cacheLife argument in Next.js 16.
Solution :
// ❌ Before
revalidateTag('posts')
// ✅ After
revalidateTag('posts', 'max')
Error :
You're importing a component that needs useState. It only works in a Client Component
Cause : Using React hooks in Server Component.
Solution : Add 'use client' directive:
// ✅ 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>
}
middleware.ts is deprecatedWarning :
Warning: middleware.ts is deprecated. Use proxy.ts instead.
Solution : Migrate to proxy.ts:
// Rename: middleware.ts → proxy.ts
// Rename function: middleware → proxy
export function proxy(request: NextRequest) {
// Same logic
}
Error :
Error: Failed to compile with Turbopack
Cause : Turbopack is now default in Next.js 16.
Solution : Opt out of Turbopack if incompatible:
npm run build -- --webpack
next/image srcError :
Invalid src prop (https://example.com/image.jpg) on `next/image`. Hostname "example.com" is not configured under images in your `next.config.js`
Solution : Add remote patterns in next.config.ts:
const config: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
},
],
},
}
Error :
You're importing a Server Component into a Client Component
Solution : Pass Server Component as children:
// ❌ 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>
generateStaticParams not workingCause : generateStaticParams only works with static generation (export const dynamic = 'force-static').
Solution :
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 }))
}
fetch() not cachingCause : Next.js 16 uses opt-in caching with "use cache" directive.
Solution : Add "use cache" to component or function:
'use cache'
export async function getPosts() {
const response = await fetch('/api/posts')
return response.json()
}
Error :
Error: Conflicting routes: /about and /(marketing)/about
Cause : Route groups create same URL path.
Solution : Ensure route groups don't conflict:
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
Cause : Using dynamic metadata without generateMetadata().
Solution : Use generateMetadata() for dynamic pages:
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,
}
}
next/font font not loadingCause : Font variable not applied to HTML element.
Solution : Apply font variable to <html> or <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>
)
}
Cause : Server-only env vars are not exposed to browser.
Solution : Prefix with NEXT_PUBLIC_ for client-side access:
# .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
Error :
Error: Could not find Server Action
Cause : Missing 'use server' directive.
Solution : Add '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({ ... })
}
Cause : Incorrect baseUrl or paths in tsconfig.json.
Solution : Configure correctly:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
"@/components/*": ["./app/components/*"]
}
}
}
See Reference : references/top-errors.md
Error : Throttling navigation to prevent the browser from hanging Source : GitHub Issue #87245
Cause : When proxy.ts (or middleware.ts) performs a redirect to add query params AND a Server Component also calls redirect() to add different query params, client-side navigation via <Link> fails in production builds. This is a regression from Next.js 14 to 16.
Symptoms :
next dev (development mode)<Link> in production buildSolution : Disable prefetch on links that navigate to pages with redirect logic:
// ✅ Workaround: Disable prefetch
<Link href="/my-route" prefetch={false}>
Navigate
</Link>
Error : Route becomes dynamic despite generateStaticParams Source : GitHub Issue #86870
Cause : Cache components ("use cache" directive) do NOT work on dynamic segments when using internationalization (i18n) frameworks like intlayer, next-intl, or lingui. Accessing params forces the route to be dynamic, even with generateStaticParams at the layout level.
Why It Happens : Every i18n framework requires accessing params to get the locale. Accessing params is an async call in Next.js 16, which opts the entire page out of caching.
Solution : Add generateStaticParams at EACH dynamic segment level:
// 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
}
Additional Context : The [locale] dynamic segment receives invalid values like _next during compilation, causing RangeError: Incorrect locale information provided when initializing i18n providers.
Error : instanceof CustomError returns false even though it is CustomError Source : GitHub Issue #87614
Cause : Module duplication in Server Components causes custom error classes to be loaded twice, creating different prototypes.
Solution : Use error.name or error.constructor.name instead of 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
}
Error : Runtime error when passing functions/class instances to Client Components Source : GitHub Issue #86748
Cause : The Next.js TypeScript plugin doesn't catch non-serializable props being passed from Server Components to Client Components. This causes runtime errors that are not detected at compile time.
Why It Happens : Only serializable data (JSON-compatible) can cross the Server/Client boundary. Functions, class instances, and Symbols cannot be serialized.
Solution : Only pass serializable props:
// ❌ 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>;
}
Runtime Validation :
import { z } from 'zod';
const UserSchema = z.object({
name: z.string(),
email: z.string(),
});
type User = z.infer<typeof UserSchema>;
Error : The 'path' argument must be of type string Source : GitHub Discussion #77721
Cause : Turbopack production builds fail with Prisma ORM (v6.5.0+).
Solution : Use webpack for production builds:
npm run build -- --webpack
Or disable Turbopack in config:
// next.config.ts
const config: NextConfig = {
experimental: {
turbo: false,
},
};
Error : Source code visible in production builds Source : GitHub Discussion #77721
Cause : Turbopack always builds production source maps for the browser, exposing source code.
Solution : Disable production source maps:
// next.config.ts
const config: NextConfig = {
productionBrowserSourceMaps: false,
};
Or exclude .map files in deployment:
# .vercelignore
*.map
Error : Module not found in production despite successful local build Source : GitHub Issue #87737
Cause : Turbopack generates external module references with hashes that don't match when node_modules structure differs (pnpm, yarn workspaces, monorepos).
Symptoms :
Solution : Explicitly externalize packages:
// next.config.ts
const config: NextConfig = {
experimental: {
serverExternalPackages: ['package-name'],
},
};
See Reference : references/top-errors.md
Next.js 16-Specific Templates (in templates/):
app-router-async-params.tsx - Async params migration patternsparallel-routes-with-default.tsx - Required default.js filescache-component-use-cache.tsx - Cache Components with "use cache"revalidate-tag-cache-life.ts - Updated revalidateTag() with cacheLifeserver-action-update-tag.ts - updateTag() for read-your-writesproxy-migration.ts - Migrate from middleware.ts to proxy.tsview-transitions-react-19.tsx - React 19.2 View TransitionsBundled References (in references/):
next-16-migration-guide.md - Complete Next.js 15→16 migration guidecache-components-guide.md - Cache Components deep diveproxy-vs-middleware.md - Proxy.ts vs middleware.tsasync-route-params.md - Async params breaking change detailsreact-19-integration.md - React 19.2 features in Next.js 16top-errors.md - 18+ common errors with solutionsExternal Documentation :
/websites/nextjs for latest reference| Package | Minimum Version | Recommended |
|---|---|---|
| Next.js | 16.0.0 | 16.1.1+ |
| React | 19.2.0 | 19.2.3+ |
| Node.js | 20.9.0 | 20.9.0+ |
| TypeScript | 5.1.0 | 5.7.0+ |
| Turbopack | (built-in) | Stable |
Check Versions :
./scripts/check-versions.sh
Estimated Token Savings : 65-70%
Without Skill (manual setup from docs):
With Skill :
Errors Prevented : 25 documented errors = 100% error prevention
Last Verified : 2026-01-21 Skill Version : 3.1.0 Changes : Added 7 new errors (navigation throttling, i18n caching, Turbopack limitations, instanceof failures, non-serializable props). Expanded async params codemod limitations, caching defaults, and parallel routes edge cases.
Next Review : 2026-04-21 (Quarterly) Maintainer : Jezweb | jeremy@jezweb.net Repository : https://github.com/jezweb/claude-skills
Update Triggers :
Version Check :
cd skills/nextjs
./scripts/check-versions.sh
End of SKILL.md
Weekly Installs
1.0K
Repository
GitHub Stars
652
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode724
claude-code720
gemini-cli699
codex649
github-copilot557
cursor546
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
Playwright 测试最佳实践指南 - 50+ 实战模式与 TypeScript/JavaScript 示例
981 周安装
Swift协议依赖注入测试:基于协议的DI模式实现可测试代码
982 周安装
maishou 买手技能:淘宝京东拼多多抖音快手全网比价,获取商品价格优惠券
983 周安装
GSAP Utils 工具函数详解:数学运算、数组处理与动画值映射 | GSAP 开发指南
983 周安装
App Store Connect 订阅批量本地化工具 - 自动化设置多语言显示名称
983 周安装
全栈安全开发指南 - Fullstack Guardian 安全编码与三视角设计实践
984 周安装
react-hook-form-zod skillnext.config.ts - Next.js 16 configuration