React, Vue.js
投稿日:

AI駆動開発でNext.jsのエラーハンドリング戦略を構築する

こんにちは、株式会社ファストコーディングのフルスタックエンジニア、独身貴族Fireです。

先日のツーリングで山道を走っていたとき、ナビアプリが圏外で固まったんです。地図は表示されない、リルートもされない、画面には何のメッセージもない。結局、勘で走って帰ってきました。

エラーが起きたときに何も伝えないのは、最悪のUXです。Webアプリも同じ。

AI駆動開発でNext.js App Routerのエラーハンドリング戦略を設計してみました。AIに「error.tsxとglobal-error.tsxの使い分け」を提案させたところ、仕組みは正しく理解していましたが、「ユーザーに何を伝えるべきか」のUX判断が甘かった。技術的に正しいエラーハンドリングと、実際のプロダクトで使えるエラーハンドリングには差があります。

Next.js App Routerのエラーハンドリングの基本

Next.js App Routerには、エラーを処理するための専用ファイルが3つあります。

ファイル役割キャッチするエラー
error.tsxセグメント単位のエラーUIそのルートセグメント内で発生したランタイムエラー
global-error.tsxアプリ全体のエラーUIルートレイアウト内のエラー(error.tsxでは捕捉できないもの)
not-found.tsx404エラーUInotFound()が呼ばれた場合、またはURLに対応するルートがない場合

重要なのは、error.tsxはClient Componentである必要がある点です。'use client'を付けなければなりません。Server Componentでレンダリング中に発生したエラーも、最も近いerror.tsxがキャッチします。

AIに設計を依頼する

AIにエラーハンドリング戦略の設計を依頼しました。ダッシュボードと設定画面を持つアプリを想定し、ユーザーにわかりやすいメッセージ、開発環境でのデバッグ情報、リトライ機能を要件として渡しました。

AIの提案と修正

error.tsx:セグメント単位のエラー

AIの初版には3つの問題がありました。英語のまま、ユーザーに何が起きたか伝えていない、開発環境でのデバッグ情報がない。修正版はこうなります。

// app/dashboard/error.tsx — 修正版
'use client';

import { useEffect } from 'react';

export default function DashboardError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error('Dashboard error:', error);
  }, [error]);

  return (
    <div className="flex flex-col items-center justify-center min-h-[400px] p-8">
      <div className="text-center max-w-md">
        <h2 className="text-xl font-bold mb-4">
          データの読み込みに失敗しました
        </h2>
        <p className="text-gray-600 mb-6">
          一時的な問題が発生しています。しばらく待ってからもう一度お試しください。
        </p>
        <button
          onClick={() => reset()}
          className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
        >
          再読み込み
        </button>
        {process.env.NODE_ENV === 'development' && (
          <details className="mt-6 text-left text-sm">
            <summary className="cursor-pointer text-gray-500">
              エラー詳細(開発環境のみ)
            </summary>
            <pre className="mt-2 p-4 bg-gray-100 rounded overflow-x-auto text-xs">
              {error.message}
              {error.stack && `\n\n${error.stack}`}
            </pre>
          </details>
        )}
      </div>
    </div>
  );
}

修正のポイントは以下の通りです。

  • ユーザーに伝わるメッセージ。「データの読み込みに失敗しました」は、何が起きたかが具体的にわかる
  • 次のアクションを示す。「しばらく待ってからもう一度お試しください」と「再読み込み」ボタンで、ユーザーが何をすべきかが明確
  • 開発環境でのデバッグ情報process.env.NODE_ENV === 'development'で出し分ける。この値はビルド時にインライン展開されるため、本番ビルドではこのブロック全体がバンドルから除去される

global-error.tsx:アプリ全体のエラー

ルートレイアウト自体でエラーが発生した場合、error.tsxではキャッチできません。global-error.tsxが必要です。

// app/global-error.tsx
'use client';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html lang="ja">
      <body>
        <div className="flex flex-col items-center justify-center min-h-screen p-8">
          <div className="text-center max-w-md">
            <h2 className="text-xl font-bold mb-4">
              予期しないエラーが発生しました
            </h2>
            <p className="text-gray-600 mb-6">
              申し訳ございません。ページを再読み込みしてください。
              問題が解決しない場合は、しばらくしてから再度お試しください。
            </p>
            <button
              onClick={() => reset()}
              className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
            >
              再読み込み
            </button>
          </div>
        </div>
      </body>
    </html>
  );
}

global-error.tsxには<html><body>タグを含める必要があります。ルートレイアウトが壊れている状態で表示されるため、レイアウトの外側で独立して動く必要があるからです。AIはこの点を正しく理解して提案してきましたが、lang="ja"を忘れていました。

not-found.tsx:404エラー

// app/not-found.tsx
import Link from 'next/link';

export default function NotFound() {
  return (
    <div className="flex flex-col items-center justify-center min-h-[400px] p-8">
      <div className="text-center max-w-md">
        <h2 className="text-xl font-bold mb-4">
          ページが見つかりません
        </h2>
        <p className="text-gray-600 mb-6">
          お探しのページは移動または削除された可能性があります。
        </p>
        <Link
          href="/"
          className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 inline-block"
        >
          トップページに戻る
        </Link>
      </div>
    </div>
  );
}

not-found.tsxerror.tsxと異なり、Server Componentで動作します。'use client'は不要です。このファイルはnotFound()関数が呼ばれたとき、またはルートが存在しないときに表示されます。

Server Componentでのエラーハンドリング

Server Componentでデータを取得する際のパターンです。

// app/dashboard/page.tsx
import { notFound } from 'next/navigation';

async function getDashboardData() {
  const res = await fetch('https://api.example.com/dashboard', {
    next: { revalidate: 60 },
  });

  if (res.status === 404) {
    notFound();
  }

  if (!res.ok) {
    throw new Error(`API error: ${res.status}`);
  }

  return res.json();
}

export default async function DashboardPage() {
  const data = await getDashboardData();

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold mb-6">ダッシュボード</h1>
      {/* データ表示 */}
    </div>
  );
}

ポイントは2つです。

  1. 404はnotFound()で処理する。throwではなくnotFound()を使うと、not-found.tsxが表示される。APIが404を返した場合、ユーザーに見せるべきは「エラーが発生しました」ではなく「ページが見つかりません」
  2. それ以外のエラーはthrowする。throwされたエラーは最も近いerror.tsxがキャッチする。try/catchで飲み込まずに、Error Boundaryに任せる

AIが見落としていたポイント

エラーメッセージの出し分け

AIは全てのerror.tsxで同じメッセージを使っていました。しかし、画面によって伝えるべきメッセージは異なります。

画面エラーメッセージ理由
ダッシュボード「データの読み込みに失敗しました」データ取得エラーが主因
設定画面「設定の保存に失敗しました」操作の失敗が主因
フォーム送信「送信に失敗しました。入力内容を確認してください」ユーザーのアクションに紐づく

AIは技術的に正しいエラー処理は書けますが、画面の文脈に応じたメッセージの使い分けはプロダクトの理解がないとできません。

reset()の制約

AIの提案ではreset()を万能のリトライ手段として扱っていましたが、実際には制約があります。reset()はReactのError Boundaryのエラー状態をクリアし、子コンポーネントの再レンダリングを試みます。ただし、データの再取得は行いません。そのため、エラーの原因がAPIの不具合であれば、reset()しても同じエラーが再発します。

実務では、reset()に加えて「トップページに戻る」リンクも用意しておくのが安全です。リトライしてもダメだった場合にユーザーが行き止まりにならないようにするためです。

まとめ

今回は、AI駆動開発でNext.js App Routerのエラーハンドリング戦略を設計した過程を紹介しました。

エラーハンドリングでAIを活用する際のポイントは以下の3つです。

  1. error.tsx、global-error.tsx、not-found.tsxの3つを適切に配置する。セグメントごとにerror.tsxを配置し、ルートレイアウト用にglobal-error.tsxを用意する。not-found.tsxで404を処理する
  2. エラーメッセージは画面の文脈に合わせる。AIは汎用的な「Something went wrong」を出すが、実際のプロダクトでは「何が失敗したか」「何をすべきか」を具体的に伝える
  3. reset()だけに頼らない。reset()はデータの再取得を行わないため、API起因のエラーからは復旧できない。トップページへのリンクなど代替の導線を用意する

私たちの開発現場では、AIにerror.tsxのテンプレートを生成させてから、画面ごとのメッセージや導線を人間が調整するフローを採っています。技術的な骨格はAIが作り、UXの判断は人間が行う。エラーハンドリングはまさにこの役割分担が効く領域です。

株式会社ファストコーディングでは、AI駆動開発を取り入れたNext.jsの設計・実装をサポートしています。「エラー画面がデフォルトのまま」「ユーザーが困るエラー表示を改善したい」という方は、お問い合わせフォームからお気軽にご相談ください。