NextRouter was not mounted
Next.js アプリケーションでルーティング操作を行う際、useRouter
フックを使おうとすると「NextRouter was not mounted」エラーが発生することがあります。この問題はバージョン13以降のNext.jsで特に顕著で、ルーティングシステムの変更に対応できていないコードが主な原因です。本記事ではこのエラーの解決方法を体系化して解説します。
問題の原因
useRouter
フックを next/router
からインポートすると、以下のエラーが発生するケースが確認されています:
// エラーが発生する典型的なコード
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のコンテキストで設定されていないために発生することがあります」
根本的な原因
- App Router導入による変更: Next.jsバージョン13 (App Router) でルーティングAPIが刷新され、
next/router
の代わりにnext/navigation
モジュールを使用する必要があります - クライアントコンポーネント不足:
useRouter
フックを含むコンポーネントで'use client'
ディレクティブが不足 - テスト環境での設定不足: ユニットテスト時、ルーターのモック実装が適切に設定されていない
解決方法
解決策1: next/navigation へのインポート変更 (Next.js 13以降)
Next.jsバージョン13で導入されたApp Routerを使用している場合、フックのインポート元を変更するだけで問題が解決します:
// 正しいインポート方法
'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では、機能ごとに別々のフックが用意されています:
'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;
}
フックの制限事項
useRouter
はクライアントコンポーネントでのみ使用可能router.events
はApp Routerでは未対応(現在開発中)next/router
のクエリオブジェクトは廃止され、useSearchParams
で置き換え
解決策3: テスト環境でのモック実装
ユニットテスト時にはRouterコンテキストをモック実装する必要があります:
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,
});
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/router | next/navigation |
クエリ取得 | router.query | useSearchParams() |
パス取得 | router.pathname | usePathname() |
ルーターイベント | 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()
を利用可能です。パス情報はヘッダーから取得できます:
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
に変更した後、プロジェクトを再起動してください。キャッシュされた型定義が原因でエラーが解消しない場合があります:
# 再起動コマンド例
npm run dev -- --clear
まとめ
「NextRouter was not mounted」エラーの本質は、新旧ルーティングシステムの混在にあります。 Next.js 13以降のApp Router導入に伴い、以下の対策が効果的です:
next/navigation
へのインポート変更- クライアントコンポーネントでの
use client
宣言 - 機能別フックの使い分け(
usePathname
,useSearchParams
等) - テスト環境でのRouterコンテキストの適切モック
App Routerの最新仕様については、公式ドキュメントの定期的な参照が重要です。