"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
文件中:
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
目录则不受影响。
主要特征
- 仅在 Next.js 13+ 的
/app
架构中出现 - 与项目布局结构和使用方式密切相关
- 控制台不会显示具体应用代码错误位置
根本原因
错误产生的原因是 Next.js 的 App Router 上下文未正确初始化,通常由以下情况导致:
- 缺失核心布局元素(如
<html>
和<body>
) - 钩子或组件在不支持的环境中使用
- 布局结构不符合 App Router 规范
- 文件放置位置不符合要求
解决方案
1. 确保根布局包含必备标签 (首选方案)
该方案解决了 90% 以上的情况
在 Next.js 13 的 App Router 架构中,根布局文件 (app/layout.tsx) 必须显式包含 <html>
和 <body>
标签。
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
错误示例
以下代码会导致路由器未正确挂载:
// ❌ 缺少 <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>
。
<html lang="en">
<body>
<AuthProvider> {/* ✅ 在 body 内部 */}
{children}
</AuthProvider>
</body>
</html>
3. 正确使用 use client
指令
当共享组件同时在 /pages
和 /app
路由中使用时,需要为 App Router 添加 use client
指令:
"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"
常见错误
// ❌ 在 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 测试时,在测试文件中添加路由模拟:
jest.mock('next/navigation'); // ✅ 模拟路由环境
// 后续测试代码...
最佳实践建议
- 分层布局规范:对于不同路由组(route groups),每个布局文件都应包含完整的 HTML 结构:
export default function AuthLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
);
}
- 禁用无效事件监听:迁移时移除旧版本的
router.events
监听:
useEffect(() => {
// ❌ 已废弃的 routeChangeStart
// router.events.on("routeChangeStart", ...)
// ✅ App Router 改用Navigation Hook
}, []);
- 检查 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 官方文档进行对照。