在使用 Next.js 16 App Router 时感到困难?更快、更聪明地迁移
Source: Dev.to
Next.js 16 实际改变了什么?
Next.js 16 引入了多个范式转变,影响了你构建应用的方式。
1. 异步 params 和 searchParams
在 Next.js 15 及之前,params 和 searchParams 是同步对象。 在 Next.js 16 中它们是 Promise,必须使用 await。
之前(Next.js 15):
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
const { slug } = params; // ✅ 同步访问
return <h1>Post: {slug}</h1>;
}
之后(Next.js 16):
// app/blog/[slug]/page.tsx
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params; // ⚠️ 现在是异步的!
return <h1>Post: {slug}</h1>;
}
为什么要这样改? 这样可以更好地进行流式渲染和并行数据获取。Next.js 能在 params 解析期间就开始渲染。
2. 动态 API 现在是异步的
cookies()、headers()、draftMode() 等函数现在返回 Promise。
之前:
import { cookies } from 'next/headers';
export async function getUser() {
const cookieStore = cookies(); // 同步
const token = cookieStore.get('auth-token');
return fetchUser(token);
}
之后:
import { cookies } from 'next/headers';
export async function getUser() {
const cookieStore = await cookies(); // 现在是异步的
const token = cookieStore.get('auth-token');
return fetchUser(token);
}
3. 缓存组件:显式而非隐式
以前页面默认会被缓存,导致“数据陈旧”的投诉。Next.js 16 颠倒了这一点:
- 所有页面默认是动态的(按请求渲染)。
- 通过
"use cache"指令显式开启缓存。
示例:缓存一个服务器组件
// app/blog/page.tsx
'use cache'; // 显式缓存此组件
export default async function BlogList() {
const posts = await fetch('https://api.example.com/posts')
.then((r) => r.json());
return (
<div>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
);
}
在配置中启用缓存组件:
// next.config.ts
const nextConfig = {
cacheComponents: true,
};
export default nextConfig;
4. 精细化的缓存 API
revalidateTag() 现在需要一个 cacheLife 配置来实现 stale‑while‑revalidate 行为。
之前:
import { revalidateTag } from 'next/cache';
revalidateTag('blog-posts'); // 简单失效
之后:
import { revalidateTag } from 'next/cache';
// 推荐:大多数场景使用 'max'
revalidateTag('blog-posts', 'max');
// 内置配置文件
revalidateTag('news-feed', 'hours');
revalidateTag('analytics', 'days');
// 自定义内联配置
revalidateTag('products', { expire: 3600 });
需要立即更新(例如用户操作后),使用全新的 updateTag() API:
import { updateTag } from 'next/cache';
// 在服务器动作中
export async function createPost(formData: FormData) {
// 创建文章...
updateTag('blog-posts'); // 立即失效缓存
}
步骤化迁移清单
一个务实的迁移现有 Next.js 应用的方案。
步骤 1:更新依赖
npm install next@16 react@19 react-dom@19
或运行官方 codemod:
npx @next/codemod@canary upgrade latest
步骤 2:将 params 和 searchParams 改为异步
在代码库中搜索所有访问 params 或 searchParams 的页面组件:
# 查找所有访问 params 的页面
grep -r "params:" app/
逐个更新组件:
// 之前
export default function Page({ params, searchParams }: PageProps) {
const { id } = params;
const { filter } = searchParams;
}
// 之后
export default async function Page({
params,
searchParams,
}: {
params: Promise<{ id: string }>;
searchParams: Promise<{ filter?: string }>;
}) {
const { id } = await params;
const { filter } = await searchParams;
}
步骤 3:更新动态 API 调用
为所有 cookies()、headers()、draftMode() 调用添加 await:
// 之前
const cookieStore = cookies();
const headersList = headers();
// 之后
const cookieStore = await cookies();
const headersList = await headers();
步骤 4:将 Middleware 迁移为 Proxy
Next.js 16 为了清晰度将 middleware.ts 改名为 proxy.ts:
mv middleware.ts proxy.ts
更新导出:
// 之前 (middleware.ts)
export default function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url));
}
// 之后 (proxy.ts)
export default function proxy(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url));
}
步骤 5:审查缓存策略
找出需要缓存的页面并添加 "use cache" 指令:
// 对于静态内容(博客、营销页面)
'use cache';
export default async function MarketingPage() {
// 该组件现在会被缓存
}
避免常见迁移陷阱
陷阱 1:混用同步和异步模式
问题: 在某些地方忘记 await,而其他地方却写对了。
解决方案: 开启严格的 TypeScript 模式,让编译器捕获此类错误。
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}
陷阱 2:缓存过度或不足
问题: 不清楚何时使用 "use cache",何时保持动态渲染。
经验法则:
- 使用
"use cache"的场景:营销页面、博客文章、产品列表、文档。 - 保持动态 的场景:用户仪表盘、实时数据、个性化内容、表单。
陷阱 3:忽视 Turbopack
Next.js 16 将 Turbopack 设为默认打包器。它在生产构建时 快 2–5 倍。如果你有自定义的 Webpack 配置,仍然可以选择使用 Webpack:
next dev --webpack