Next.js 中解决 "NextRouter was not mounted" 错误
问题描述
当在 Next.js 项目中使用路由相关功能时,不少开发者会遇到 "NextRouter was not mounted" 的错误。这个问题通常表现为以下形式:
typescript
Argument of type '{ pathname: string; query: { search: string; }; }' is not assignable to parameter of type 'string'.
根据 Next.js 官方文档说明,此错误通常发生在两种场景中:
- 在 Next.js 应用之外使用了路由相关的钩子组件
- 组件被渲染在 Next.js 上下文环境之外(例如单元测试场景)
常见触发场景:
- 使用
import { useRouter } from "next/router"
引入路由钩子 - 尝试在应用目录 (app directory) 中使用旧的页面目录 (pages directory) 的路由API
- 未正确处理 Next.js 13+ 版本的路由变更
解决方案
1. 升级到 Next.js 13+ 的正确路由导入方式
核心变更
Next.js 13 引入了全新的应用路由架构,路由钩子的导入路径从 next/router
变更为 next/navigation
步骤:
- 在组件顶部添加客户端指令
- 从
next/navigation
导入路由钩子
jsx
'use client'; // 必须声明为客户端组件
import { useRouter } from 'next/navigation';
export default function SearchButton() {
const router = useRouter();
const [searchInput, setSearchInput] = useState("");
const handleSearch = () => {
// 新的使用方式(仅支持字符串URL)
router.push(`/search?query=${encodeURIComponent(searchInput)}`);
};
return (
<button onClick={handleSearch}>搜索</button>
);
}
js
import { useRouter } from 'next/router';
js
import { useRouter } from 'next/navigation';
2. 使用 URLSearchParams 处理查询参数
在应用目录 (app directory) 架构下,router.push()
不再接受对象参数,仅支持字符串URL:
js
// 构造查询字符串
const params = new URLSearchParams({ search: searchInput });
router.push(`/search?${params.toString()}`);
错误用法
js
// ❌ 不再支持的对象参数格式
router.push({
pathname: '/search',
query: { search: searchInput }
})
3. 获取路径和查询参数的新方法
路由信息获取变化
在应用目录中,路径名和查询参数需要使用专用钩子获取:
jsx
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
export default function ProductHeader() {
const pathname = usePathname(); // 获取当前路径
const searchParams = useSearchParams(); // 获取查询参数
const searchTerm = searchParams.get('search');
return (
<div>
<p>当前路径: {pathname}</p>
<p>搜索关键词: {searchTerm || '无'}</p>
</div>
);
}
4. 单元测试场景的解决方案
在测试环境中,需要模拟路由上下文:
typescript
import { render, screen } from '@testing-library/react';
import { RouterContext } from 'next/dist/shared/lib/router-context';
// 创建模拟路由
const mockRouter = {
pathname: '/test',
push: jest.fn(),
// 添加其他需要的路由属性...
};
test('带路由组件的测试', () => {
render(
<RouterContext.Provider value={mockRouter as any}>
<YourComponent />
</RouterContext.Provider>
);
// 执行测试断言
});
5. 服务器端和客户端路由区分
架构变化
应用目录默认采用服务器组件,只能在客户端组件中使用路由钩子
jsx
// app/search/page.js (服务端组件)
import SearchForm from './SearchForm';
export default function SearchPage() {
// ❌ 不能在服务端组件中使用路由钩子
return <SearchForm />;
}
// app/search/SearchForm.js (客户端组件)
'use client';
import { useRouter } from 'next/navigation';
export default function SearchForm() {
// ✅ 客户端组件中可使用路由钩子
const router = useRouter();
// 处理逻辑...
}
版本变更总结
常见错误排查清单
- 已添加 'use client' 指令? → 客户端组件必需
- 安装了正确版本? → 运行
npm list next
确认版本 - 导入路径是否正确? → 区分
next/router
和next/navigation
- 是否在服务器组件使用路由钩子? → 检查文件位置
- 参数传递方式是否正确? → 应用目录仅支持字符串URL
最佳实践建议
项目升级策略:
- 从 Next.js 12 升级到 13+ 时,逐步迁移路由代码
- 使用
next.config.js
中的appDir
标志启用新特性
路由代码组织:
typescript// utils/routing.ts export const createSearchURL = (params: Record<string, string>) => { const searchParams = new URLSearchParams(params); return `/search?${searchParams.toString()}`; };
类型安全:
typescriptimport { useRouter } from 'next/navigation'; declare module 'next/navigation' { export type AppRouter = ReturnType<typeof useRouter>; }
通过以上方案,您可以彻底解决 Next.js 中的 "NextRouter was not mounted" 错误,并正确使用应用目录下的路由系统。新的路由架构虽需要适应,但带来了显著的性能优化和改进的开发体验。