在使用 Next.js 16 App Router 时感到困难?更快、更聪明地迁移

发布: (2025年11月30日 GMT+8 06:07)
6 min read
原文: Dev.to

Source: Dev.to

Next.js 16 实际改变了什么?

Next.js 16 引入了多个范式转变,影响了你构建应用的方式。

1. 异步 paramssearchParams

在 Next.js 15 及之前,paramssearchParams 是同步对象。 在 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:将 paramssearchParams 改为异步

在代码库中搜索所有访问 paramssearchParams 的页面组件:

# 查找所有访问 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
Back to Blog

相关文章

阅读更多 »

Bf-Trees:突破页面壁垒

你好,我是Maneshwar。我正在开发FreeDevTools——一个在线的开源中心,将 dev tools、cheat codes 和 TLDR 汇集在一个地方,方便……