Next.js App Routerマウントエラーの解決法
問題の説明
Next.js 12から13に移行する際に、新たに導入された/app
ディレクトリで以下のエラーが発生します:
Uncaught Error: invariant expected app router to be mounted
このエラーは、next/navigation
パッケージのuseRouter
フックがコンテキストからルーターオブジェクトを取得しようとした際にnull
が返ることで発生します:
function useRouter() {
const router = (0, _react).useContext(_appRouterContext.AppRouterContext);
if (router === null) {
throw new Error('invariant expected app router to be mounted');
}
return router;
}
特筆すべき点:
/pages
ディレクトリでは問題なく動作する/app
ディレクトリを使ったApp Router環境でのみ発生- レイアウトファイル(layout.tsx)の問題が主な原因
主要な解決策
1. ルートレイアウトに<html>
と<body>
タグを追加
Next.js 13のApp Routerでは、ルートレイアウトに<html>
と<body>
タグが必須です。これらのタグがないとアプリケーションが正しく初期化されません:
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="ja">
<body>
{/* ここにヘッダーやナビゲーションなどを配置 */}
{children}
</body>
</html>
);
}
間違った例
以下のように<html>
や<body>
を欠くとエラーが発生します:
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<>
<header>ヘッダー</header>
<main>{children}</main>
</>
);
}
公式ドキュメントでも明記されています:
ルートレイアウトには<html>と<body>タグが必要です。Next.jsはこれらを自動的に作成しないため明示的に定義する必要があります。
2. <body>
タグの外側にコンポーネントを配置しない
グローバルコンテキストプロバイダーなどのコンポーネントは、必ず<body>
タグの内側に配置する必要があります:
<html>
<body>
{/* 正しい:コンポーネントはbody内に */}
<AuthProvider>
<GlobalStateProvider>
{children}
</GlobalStateProvider>
</AuthProvider>
</body>
</html>
間違った配置
以下のようにコンポーネントを<body>
タグの外側に配置するとエラーが発生します:
<html>
{/* 誤り:AuthProvierがbodyの外側 */}
<AuthProvider>
<body>
{children}
</body>
</AuthProvider>
</html>
3. App RouterとPages Routerの混在に対する対処
プロジェクトで両方のルーターを使用している場合、共有コンポーネントには'use client'
ディレクティブを追加します:
'use client'; // 必須ディレクティブ
import { useRouter } from 'next/navigation';
export default function SharedComponent() {
const router = useRouter();
// ...
}
その他の原因と解決策
パラレルルートの設定ミス
パラレルルートを使用している場合、スロットごとにdefault.js
ファイルが必須です:
app/
├─ @analytics/ ← パラレルルートスロット
│ ├─ dashboard/
│ │ └─ page.js
│ └─ default.js ← 必須ファイル
テスト環境(Jest)での対応
ユニットテストでこのエラーが発生する場合、next/navigation
をモックします:
jest.mock('next/navigation');
StoryBookでの設定
Next.js App RouterをStoryBookで使用するには、設定ファイルを更新します:
const preview = {
parameters: {
nextjs: {
appDirectory: true, // App Routerを有効化
},
},
};
next/router
とnext/navigation
の混同
App Router(/app
ディレクトリ)とPages Router(/pages
ディレクトリ)ではルーターのインポート元が異なります:
- import { useRouter } from 'next/router'; // Pages Router向け
+ import { useRouter } from 'next/navigation'; // App Router向け
ローディングファイルの配置ミス
loading.tsx
ファイルはルートディレクトリではなく、ページコンポーネントと同じ階層に配置します:
app/
├─ layout.tsx
└─ dashboard/
├─ page.tsx
└─ loading.tsx // ← ここに配置
よくある間違いと修正例
レイアウトファイルの典型的な間違い
export default function RootLayout({ children }) {
return (
<html lang="en">
<div className={styles.container}>
<header>{/* ... */}</header>
<main>{children}</main>
</div>
</html>
);
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<div className={styles.container}>
<header>{/* ... */}</header>
<main>{children}</main>
</div>
</body>
</html>
);
}
まとめ
「Uncaught Error: invariant expected app router to be mounted」エラーの主な解決ポイント:
<html>
と<body>
タグをルートレイアウトに含める - 最も一般的な根本原因- コンポーネントは常に
<body>
内に配置 - 外側に配置しない - 混在ルーター環境では
'use client'
を明示 - パラレルルートでは
default.js
を欠かさない - App Router環境では
next/navigation
をインポート
Next.js 13のApp Routerはパフォーマンスと開発体験を大幅に改善しますが、移行時はこれらの点に注意することでスムーズにアップグレードできます。