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はパフォーマンスと開発体験を大幅に改善しますが、移行時はこれらの点に注意することでスムーズにアップグレードできます。