Menu

エラーハンドリング

エラーは予期されるエラーキャッチされない例外の2つのカテゴリに分けられます:

  • 予期されるエラーは戻り値としてモデル化する: サーバーアクションでは、予期されるエラーに対して try/catch を使用せず、useFormState を使用してこれらのエラーを管理し、クライアントに返します。
  • 予期しないエラーにはエラーバウンダリを使用する: error.tsxglobal-error.tsx ファイルを使用してエラーバウンダリを実装し、予期しないエラーを処理し、フォールバックUIを提供します。

予期されるエラーの処理

予期されるエラーは、サーバー側のフォーム検証や失敗したリクエストなど、アプリケーションの通常の動作中に発生する可能性のあるエラーです。これらのエラーは明示的に処理され、クライアントに返される必要があります。

サーバーアクションからの予期されるエラーの処理

useFormState フックを使用して、サーバーアクションの状態(エラーを含む)を管理します。この方法により、予期されるエラーに対して try/catch ブロックを避け、例外としてスローするのではなく、戻り値としてモデル化します。

app/actions.ts
TypeScript
'use server'
 
import { redirect } from 'next/navigation'
 
export async function createUser(prevState: any, formData: FormData) {
  const res = await fetch('https://...')
  const json = await res.json()
 
  if (!res.ok) {
    return { message: '有効なメールアドレスを入力してください' }
  }
 
  redirect('/dashboard')
}

次に、アクションを useFormState フックに渡し、返された state を使用してエラーメッセージを表示できます。

app/ui/signup.tsx
TypeScript
'use client'
 
import { useFormState } from 'react'
import { createUser } from '@/app/actions'
 
const initialState = {
  message: '',
}
 
export function Signup() {
  const [state, formAction] = useFormState(createUser, initialState)
 
  return (
    <form action={formAction}>
      <label htmlFor="email">メールアドレス</label>
      <input type="text" id="email" name="email" required />
      {/* ... */}
      <p aria-live="polite">{state?.message}</p>
      <button>サインアップ</button>
    </form>
  )
}

補足: これらの例は、Next.jsのApp RouterにバンドルされているReactの useFormState フックを使用しています。React 19を使用している場合は、代わりに useActionState を使用してください。詳細はReactのドキュメントを参照してください。

返された状態を使用して、クライアントコンポーネントからトーストメッセージを表示することもできます。

サーバーコンポーネントからの予期されるエラーの処理

サーバーコンポーネント内でデータをフェッチする場合、レスポンスを使用してエラーメッセージを条件付きでレンダリングしたり、redirectしたりできます。

app/page.tsx
TypeScript
export default async function Page() {
  const res = await fetch(`https://...`)
  const data = await res.json()
 
  if (!res.ok) {
    return 'エラーが発生しました。'
  }
 
  return '...'
}

キャッチされない例外

キャッチされない例外は、予期しないエラーであり、アプリケーションの通常のフローでは発生すべきでないバグや問題を示します。これらは例外をスローすることで処理され、エラーバウンダリによってキャッチされます。

  • 一般的: ルートレイアウトの下にある error.js でキャッチされないエラーを処理します。
  • オプション: ネストされた error.js ファイル(例: app/dashboard/error.js)でキャッチされないエラーを細かく処理します。
  • まれ: global-error.js を使用してルートレイアウトのキャッチされないエラーを処理します。

エラーバウンダリの使用

Next.jsはキャッチされない例外を処理するためにエラーバウンダリを使用します。エラーバウンダリは子コンポーネントのエラーをキャッチし、クラッシュしたコンポーネントツリーの代わりにフォールバックUIを表示します。

エラーバウンダリを作成するには、ルートセグメント内に error.tsx ファイルを追加し、Reactコンポーネントをエクスポートします:

app/dashboard/error.tsx
TypeScript
'use client' // エラーバウンダリはクライアントコンポーネントである必要があります
 
import { useEffect } from 'react'
 
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // エラー報告サービスにエラーをログ出力
    console.error(error)
  }, [error])
 
  return (
    <div>
      <h2>問題が発生しました!</h2>
      <button
        onClick={
          // セグメントの再レンダリングによって復旧を試みる
          () => reset()
        }
      >
        再試行
      </button>
    </div>
  )
}

error コンポーネントをレンダリングする際にスローすることで、親のエラーバウンダリにエラーを伝播させることができます。

ネストされたルートでのエラー処理

エラーは最も近い親のエラーバウンダリに伝播します。これにより、ルート階層の異なるレベルに error.tsx ファイルを配置することで、細かいエラー処理が可能になります。

ネストされたエラーコンポーネントの階層

グローバルエラーの処理

あまり一般的ではありませんが、国際化を活用する際でも、ルートアプリディレクトリの app/global-error.js を使用してルートレイアウトのエラーを処理できます。グローバルエラーUIは、アクティブ時にルートレイアウトまたはテンプレートを置き換えるため、独自の <html> タグと <body> タグを定義する必要があります。

app/global-error.tsx
TypeScript
'use client' // エラーバウンダリはクライアントコンポーネントである必要があります
 
export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    // global-error は html と body タグを含める必要があります
    <html>
      <body>
        <h2>問題が発生しました!</h2>
        <button onClick={() => reset()}>再試行</button>
      </body>
    </html>
  )
}