Skip to content

"invariant expected app router to be mounted" 错误解决方案

问题描述

当将 Next.js 12 项目迁移到 Next.js 13 并使用新的 /app 目录路由架构时,您可能会在控制台遇到以下错误:

Uncaught Error: invariant expected app router to be mounted

该错误通常发生在使用 useRouter 钩子时,具体抛出位置在 navigation.js 文件中:

javascript
function useRouter() {
    const router = (0, _react).useContext(_appRouterContext.AppRouterContext);
    if (router === null) {
        throw new Error('invariant expected app router to be mounted');
    }
    return router;
}

此错误表明 Next.js 的 App Router 上下文未正确挂载,主要影响出现在 /app 目录下的组件,而 /pages 目录则不受影响。

主要特征

  1. 仅在 Next.js 13+ 的 /app 架构中出现
  2. 与项目布局结构和使用方式密切相关
  3. 控制台不会显示具体应用代码错误位置

根本原因

错误产生的原因是 Next.js 的 App Router 上下文未正确初始化,通常由以下情况导致:

  • 缺失核心布局元素(如 <html><body>
  • 钩子或组件在不支持的环境中使用
  • 布局结构不符合 App Router 规范
  • 文件放置位置不符合要求

解决方案

1. 确保根布局包含必备标签 (首选方案)

该方案解决了 90% 以上的情况
在 Next.js 13 的 App Router 架构中,根布局文件 (app/layout.tsx) 必须显式包含 <html><body> 标签。

jsx
export default function RootLayout({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

错误示例

以下代码会导致路由器未正确挂载:

jsx
// ❌ 缺少 <html> 和 <body>
export default function RootLayout({ children }) {
  return (
    <div className="container">
      <header>...</header>
      <main>{children}</main>
    </div>
  );
}

根据 Next.js 官方文档

根布局必须定义 <html><body> 标签,因为 Next.js 不会自动创建它们。

2. 正确处理包裹组件位置

任何状态管理或上下文组件(如 Redux 或 Auth Provider)必须在 <body> 标签内部使用,而不是包裹整个 <body>

jsx
<html lang="en">
  <body>
    <AuthProvider>  {/* ✅ 在 body 内部 */}
      {children}
    </AuthProvider>
  </body>
</html>

3. 正确使用 use client 指令

当共享组件同时在 /pages/app 路由中使用时,需要为 App Router 添加 use client 指令:

jsx
"use client"; // ✅ 必须添加

import { useRouter } from "next/navigation";

export default function SharedComponent() {
  const router = useRouter();
  // ...
}

4. 检查路由钩子导入路径

确保根据你使用的路由模式导入正确的钩子:

  • App Router (/app 目录): import { useRouter } from "next/navigation"
  • Pages Router (/pages 目录): import { useRouter } from "next/router"

常见错误

javascript
// ❌ 在 Pages Router 中错误导入
import { useRouter } from "next/navigation"; 

// ✅ 正确导入
import { useRouter } from "next/router";

5. 确保并行路由存在默认文件

当使用 并行路由 时,每个插槽(slot)必须包含 default.js 文件:

app
├── @sidebar
│   └── default.js  ✅ 必须存在
└── layout.js

6. 正确放置 loading 文件

loading.jsx/tsx 文件应放置在页面目录内部,而非根目录:

app
├── layout.tsx
└── dashboard
    ├── page.tsx
    └── loading.tsx  ✅ 正确位置

测试环境特殊处理

使用 Jest 测试时,在测试文件中添加路由模拟:

javascript
jest.mock('next/navigation'); // ✅ 模拟路由环境

// 后续测试代码...

最佳实践建议

  1. 分层布局规范:对于不同路由组(route groups),每个布局文件都应包含完整的 HTML 结构:
jsx
export default function AuthLayout({ children }) {
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}
  1. 禁用无效事件监听:迁移时移除旧版本的 router.events 监听:
javascript
useEffect(() => {
  // ❌ 已废弃的 routeChangeStart
  // router.events.on("routeChangeStart", ...)
  
  // ✅ App Router 改用Navigation Hook
}, []);
  1. 检查 DOM 操作位置:避免在 useEffect 中直接操作 document.body(可能导致布局不一致)。

迁移注意事项

  • 官方已废弃 next/head→ 使用内置的 <Head> 组件替代
  • next/image 组件在 layout.js 中需配合特殊加载配置
  • 确保所有客户端组件显式声明 "use client"

通过实施这些解决方案,您应该能成功解决 invariant expected app router to be mounted 错误。迁移到 App Router 架构需要遵循新的项目结构规范,特别关注布局文件的结构要求。遇到问题时,可参考 Next.js 的 App Router 官方文档进行对照。