Skip to content

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 官方文档说明,此错误通常发生在两种场景中:

  1. 在 Next.js 应用之外使用了路由相关的钩子组件
  2. 组件被渲染在 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

步骤:

  1. 在组件顶部添加客户端指令
  2. 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();
  
  // 处理逻辑...
}

版本变更总结

常见错误排查清单

  1. 已添加 'use client' 指令? → 客户端组件必需
  2. 安装了正确版本? → 运行 npm list next 确认版本
  3. 导入路径是否正确? → 区分 next/routernext/navigation
  4. 是否在服务器组件使用路由钩子? → 检查文件位置
  5. 参数传递方式是否正确? → 应用目录仅支持字符串URL

最佳实践建议

  1. 项目升级策略

    • 从 Next.js 12 升级到 13+ 时,逐步迁移路由代码
    • 使用 next.config.js 中的 appDir 标志启用新特性
  2. 路由代码组织

    typescript
    // utils/routing.ts
    export const createSearchURL = (params: Record<string, string>) => {
      const searchParams = new URLSearchParams(params);
      return `/search?${searchParams.toString()}`;
    };
  3. 类型安全

    typescript
    import { useRouter } from 'next/navigation';
    
    declare module 'next/navigation' {
      export type AppRouter = ReturnType<typeof useRouter>;
    }

通过以上方案,您可以彻底解决 Next.js 中的 "NextRouter was not mounted" 错误,并正确使用应用目录下的路由系统。新的路由架构虽需要适应,但带来了显著的性能优化和改进的开发体验。