Skip to content

Next.js 15 中解决动态路由参数 await 错误

问题描述

在使用 Next.js 15 的应用路由(App Router)实现动态路由本地化时,访问路由参数 params.locale 会遇到以下错误:

bash
Error: Route "/[locale]" used `params.locale`. `params` should be awaited before using its properties. 
Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis
    at locale (webpack:///app/[locale]/layout.tsx?a262:30:34)

典型项目结构:

bash
app/
├── [locale]/
   ├── layout.tsx            # 根布局文件
   └── page.tsx              # 主页面组件
locales/
├── en/
   └── common.json
├── lt/
   └── common.json

布局文件常包含以下形式的代码:

tsx
// app/[locale]/layout.tsx
export default async function RootLayout({
  children,
  params: { locale }, // 直接解构参数
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  return (
    <html lang={locale}>  // 错误触发位置
      ...
    </html>
  );
}

常见尝试解决方案:

  • 将参数提取为局部变量
  • 使用 generateStaticParams 预生成路由
  • 添加参数类型声明
  • 重启开发服务器

但上述方法均无法解决根本问题。

解决方案

正确 await 参数对象

Next.js 15 中,动态路由参数以异步 Promise 形式传递,必须显式 await:

tsx
// app/[locale]/layout.tsx - 修正后
export default async function RootLayout({
  children,
  params, // 不要直接解构!
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>; // 声明为Promise类型
}) {
  const { locale } = await params; // 关键:await 参数

  return (
    <html lang={locale}>  // 正确使用
      ...
    </html>
  );
}

同样适用于页面组件:

tsx
// app/[locale]/page.tsx
export default async function HomePage({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params; // 正确获取参数

  return (
    <div>
      ...
    </div>
  );
}

重要提示

即使函数已声明为 async,也必须显式 await params

检查静态文件位置

某些静态文件放置在动态路由目录下会意外触发此错误:

正确结构

bash
app/
  icon.png        # 静态资源在根目录
  [locale]/
    layout.tsx

错误结构

bash
app/
  [locale]/
    layout.tsx
    icon.png     # 静态资源触发错误

解决方法:将所有静态资源(favicon、图片等)移至 app/ 根目录

Why?

Next.js 会将动态路由中的静态文件误识别为需动态处理的请求

使用代码修复工具

Next.js 官方提供自动修复工具:

bash
npx @next/codemod@canary next-async-request-api

此工具会自动检测并修复代码库中的 params 使用问题

API 路由的正确处理

对于 POSTGET 等 API 路由,同样需 await params:

tsx
export async function POST(
  req: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params; // 解构前 await
  return NextResponse.json({ id });
}

原理解析

Next.js 15 对动态路由参数处理机制进行了重要升级:

  1. 异步参数加载

    • 路由参数现在异步解析
    • params 对象被包装在 Promise 中
    • 必须等待解析完成才能访问属性
  2. 静态文件冲突

    • 放置于动态路由目录中的静态文件会被当作战利品收集行为
    • 触发虚拟路由请求导致参数处理异常
  3. 官方迁移方案

    • next-async-request-api codemod 专门处理此变更
    • 适配 Next.js 生态的异步演进要求

注意事项

  • 仅针对 Next.js 15+ 版本有效
  • Pages Router 不受此变更影响
  • 开发环境可能需要清理缓存重新启动

最佳实践建议

  1. 统一参数处理

    tsx
    interface ParamsProps {
      params: Promise<{ 
        locale: string 
        // ...其他参数
      }>;
    }
    
    export default async function Layout({ params }: ParamsProps) {
      const resolvedParams = await params;
      // 使用 resolvedParams.locale
    }
  2. 创建参数解析钩子

    tsx
    // hooks/useRouteParams.ts
    export default async function useRouteParams(
      params: Promise<{ locale: string }>
    ) {
      return await params;
    }
    
    // layout.tsx
    import useRouteParams from '@/hooks/useRouteParams';
    
    export default async function Layout({ params }) {
      const { locale } = await useRouteParams(params);
    }
  3. 文件结构规范

    bash
    app/
    public/
      favicon.ico  # 推荐位置
    app/
      [locale]/    
        # 只包含需要动态处理的路由文件
      assets/      # 静态资源目录
        icons/
  4. 升级兼容性检查

    json
    // package.json
    "dependencies": {
      "next": "^15.0.0", 
      "react": "^19.0.0",
      "react-dom": "^19.0.0"
    }

遵循这些解决方案可彻底解决路由参数访问问题,同时确保代码符合 Next.js 15 的最新规范要求。