Skip to content

NextRouter was not mounted

Next.js アプリケーションでルーティング操作を行う際、useRouter フックを使おうとすると「NextRouter was not mounted」エラーが発生することがあります。この問題はバージョン13以降のNext.jsで特に顕著で、ルーティングシステムの変更に対応できていないコードが主な原因です。本記事ではこのエラーの解決方法を体系化して解説します。

問題の原因

useRouter フックを next/router からインポートすると、以下のエラーが発生するケースが確認されています:

typescript
// エラーが発生する典型的なコード
import { useRouter } from "next/router"; // 問題のインポート先

const Component = () => {
  const router = useRouter();
  
  const handleNavigate = () => {
    // TypeScriptエラー: Objectはstringに割り当て不可
    router.push({
      pathname: '/search',
      query: { search: "keyword" }
    });
  }
}

公式ドキュメントでは、このエラーは以下の状況で発生すると説明されています:

「コンポーネントがNext.jsアプリケーション外でuseRouterを使用した場合、またはNext.jsアプリケーションの外でレンダリングされた場合に発生する可能性があります。これは、useRouterフックを使用するコンポーネントをユニットテストする場合に、Next.jsのコンテキストで設定されていないために発生することがあります」

根本的な原因

  1. App Router導入による変更: Next.jsバージョン13 (App Router) でルーティングAPIが刷新され、next/router の代わりに next/navigation モジュールを使用する必要があります
  2. クライアントコンポーネント不足: useRouter フックを含むコンポーネントで 'use client' ディレクティブが不足
  3. テスト環境での設定不足: ユニットテスト時、ルーターのモック実装が適切に設定されていない

解決方法

解決策1: next/navigation へのインポート変更 (Next.js 13以降)

Next.jsバージョン13で導入されたApp Routerを使用している場合、フックのインポート元を変更するだけで問題が解決します:

jsx
// 正しいインポート方法
'use client'; // クライアントコンポーネント指定必須
import { useRouter } from 'next/navigation'; // インポート元を変更

const SearchButton = () => {
  const router = useRouter();
  const [searchInput, setSearchInput] = useState("");

  const search = () => {
    // オブジェクト形式で正常に動作
    router.push({
      pathname: '/search',
      query: { search: searchInput }
    });
  };

  return (
    // ... ボタンや入力コンポーネント ...
  );
}

バージョン対応表

Next.js バージョン使用するインポート備考
v12 (Pages Router)next/router従来通りの使用法
v13+ (App Router)next/navigationクライアントコンポーネント必須

解決策2: 必要なフックを使い分ける

App Routerでは、機能ごとに別々のフックが用意されています:

js
'use client';
import { 
  useRouter,      // router.push()など
  usePathname,    // 現在のパス取得
  useSearchParams // クエリパラメータ操作
} from 'next/navigation';

const CurrentPathDisplay = () => {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  
  const searchQuery = searchParams.get('search') || '';

  // pathnameの利用例: /searchの時にメッセージ表示
  if (pathname === '/search') {
    return <div>{searchQuery}で検索中</div>;
  }
  
  return null;
}

フックの制限事項

  1. useRouter はクライアントコンポーネントでのみ使用可能
  2. router.events はApp Routerでは未対応(現在開発中)
  3. next/router のクエリオブジェクトは廃止され、useSearchParams で置き換え

解決策3: テスト環境でのモック実装

ユニットテスト時にはRouterコンテキストをモック実装する必要があります:

typescript
import { NextRouter } from 'next/router';

export const mockNextRouter = (router: Partial<NextRouter>): NextRouter => ({
  basePath: '',
  route: '/',
  pathname: '/',
  query: {},
  asPath: '/',
  push: jest.fn(),
  replace: jest.fn(),
  reload: jest.fn(),
  back: jest.fn(),
  prefetch: jest.fn(),
  beforePopState: jest.fn(),
  events: { on: jest.fn(), off: jest.fn(), emit: jest.fn() },
  isFallback: false,
  isLocaleDomain: false,
  isReady: true,
  defaultLocale: 'en',
  domainLocales: [],
  isPreview: false,
  ...router,
});
tsx
import { render, screen } from '@testing-library/react';
import { RouterContext } from 'next/dist/shared/lib/router-context';
import { mockNextRouter } from '../test-utils/mock-next-router';
import SearchComponent from '../components/search';

describe('Searchコンポーネントテスト', () => {
  it('検索ボタンクリックで/navigateが呼ばれる', () => {
    const mockedRouter = mockNextRouter({});
    render(
      <RouterContext.Provider value={mockedRouter}>
        <SearchComponent />
      </RouterContext.Provider>
    );

    const searchButton = screen.getByRole('button', { name: '検索' });
    fireEvent.click(searchButton);
    
    expect(mockedRouter.push).toHaveBeenCalledWith({
      pathname: '/search',
      query: { search: 'test' }
    });
  });
});

App Router vs Pages Routerの主要変更点

移行時の重要な違い

機能Pages Router (pages/)App Router (app/)
useRouter インポートnext/routernext/navigation
クエリ取得router.queryuseSearchParams()
パス取得router.pathnameusePathname()
ルーターイベントrouter.events未実装
ディレクトリ構成/pages/search.js/app/search/page.js

よくある質問

Q. useRouter はどこまで代替可能ですか?

A. 現在、App RouterのuseRouterで利用可能なのはpush(), replace(), refresh(), prefetch(), back() forward()メソッドのみです。以前のイベントリスナー機能など未実装機能については、今後のアップデートに注目する必要があります。

Q. サーバーコンポーネントでパス情報を取得できますか?

A. サーバーコンポーネントではnext/headersからheaders()cookies()を利用可能です。パス情報はヘッダーから取得できます:

tsx
import { headers } from 'next/headers';

export default function ServerPath() {
  const headersList = headers();
  const pathname = headersList.get('x-pathname') || '';
  
  return (
    <div>
      現在のパス: {pathname}
    </div>
  );
}

Q. 移行後もTypeScriptエラーが出るのはなぜ?

A. next/router から next/navigation に変更した後、プロジェクトを再起動してください。キャッシュされた型定義が原因でエラーが解消しない場合があります:

bash
# 再起動コマンド例
npm run dev -- --clear

まとめ

「NextRouter was not mounted」エラーの本質は、新旧ルーティングシステムの混在にあります。 Next.js 13以降のApp Router導入に伴い、以下の対策が効果的です:

  1. next/navigation へのインポート変更
  2. クライアントコンポーネントでの use client 宣言
  3. 機能別フックの使い分け(usePathname, useSearchParams等)
  4. テスト環境でのRouterコンテキストの適切モック

App Routerの最新仕様については、公式ドキュメントの定期的な参照が重要です。