Skip to content

Next.js App Routerマウントエラーの解決法

問題の説明

Next.js 12から13に移行する際に、新たに導入された/appディレクトリで以下のエラーが発生します:

Uncaught Error: invariant expected app router to be mounted

このエラーは、next/navigationパッケージのuseRouterフックがコンテキストからルーターオブジェクトを取得しようとした際にnullが返ることで発生します:

javascript
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>タグが必須です。これらのタグがないとアプリケーションが正しく初期化されません:

tsx
export default function RootLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ja">
      <body>
        {/* ここにヘッダーやナビゲーションなどを配置 */}
        {children}
      </body>
    </html>
  );
}

間違った例

以下のように<html><body>を欠くとエラーが発生します:

tsx
// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <>
      <header>ヘッダー</header>
      <main>{children}</main>
    </>
  );
}

公式ドキュメントでも明記されています:

ルートレイアウトには<html>と<body>タグが必要です。Next.jsはこれらを自動的に作成しないため明示的に定義する必要があります。

2. <body>タグの外側にコンポーネントを配置しない

グローバルコンテキストプロバイダーなどのコンポーネントは、必ず<body>タグの内側に配置する必要があります:

tsx
<html>
  <body>
    {/* 正しい:コンポーネントはbody内に */}
    <AuthProvider>
      <GlobalStateProvider>
        {children}
      </GlobalStateProvider>
    </AuthProvider>
  </body>
</html>

間違った配置

以下のようにコンポーネントを<body>タグの外側に配置するとエラーが発生します:

tsx
<html>
  {/* 誤り:AuthProvierがbodyの外側 */}
  <AuthProvider>
    <body>
      {children}
    </body>
  </AuthProvider>
</html>

3. App RouterとPages Routerの混在に対する対処

プロジェクトで両方のルーターを使用している場合、共有コンポーネントには'use client'ディレクティブを追加します:

tsx
'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をモックします:

javascript
jest.mock('next/navigation');

StoryBookでの設定

Next.js App RouterをStoryBookで使用するには、設定ファイルを更新します:

ts
const preview = {
  parameters: {
    nextjs: {
      appDirectory: true, // App Routerを有効化
    },
  },
};

next/routernext/navigationの混同

App Router(/appディレクトリ)とPages Router(/pagesディレクトリ)ではルーターのインポート元が異なります:

diff
- import { useRouter } from 'next/router'; // Pages Router向け
+ import { useRouter } from 'next/navigation'; // App Router向け

ローディングファイルの配置ミス

loading.tsxファイルはルートディレクトリではなく、ページコンポーネントと同じ階層に配置します:

app/
├─ layout.tsx
└─ dashboard/
   ├─ page.tsx
   └─ loading.tsx  // ← ここに配置

よくある間違いと修正例

レイアウトファイルの典型的な間違い

tsx
export default function RootLayout({ children }) {
  return (
    <html lang="en">
    <div className={styles.container}>
      <header>{/* ... */}</header>
      <main>{children}</main>
    </div>
    </html>
  );
}
tsx
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」エラーの主な解決ポイント:

  1. <html><body>タグをルートレイアウトに含める - 最も一般的な根本原因
  2. コンポーネントは常に<body>内に配置 - 外側に配置しない
  3. 混在ルーター環境では'use client'を明示
  4. パラレルルートではdefault.jsを欠かさない
  5. App Router環境ではnext/navigationをインポート

Next.js 13のApp Routerはパフォーマンスと開発体験を大幅に改善しますが、移行時はこれらの点に注意することでスムーズにアップグレードできます。