にょっす速報🐮✋

Next.js App Routerでエラーハンドリングを最適化する方法

この記事の目次

Next.js の App Router を使用している際、エラーハンドリングの方法に悩んでいる開発者向けの記事です。本記事では、**「カスタムエラーをシリアライズしてクライアントコンポーネントに受け渡し、再スローする」**という実装例を紹介します。この方法により、エラー内容を効率的にユーザーに伝えつつ、スムーズなデバッグを実現できます。

対象読者

  • Next.js の App Router を使用している方
  • サーバーサイドとクライアントサイド間でのエラー管理に課題を感じている方

環境情報

以下の環境で動作確認を行っています:

"next": "14.2.4",
"react": "18",
"typescript": "5",

結論

今回のエラーハンドリングの方法では、以下のフローを採用しました:

  1. サーバーコンポーネントでエラーを発生させる。
  2. エラーをカスタムクラスにシリアライズ
  3. クライアントコンポーネントで再スローし、適切なUIでエラーを表示。

この方法により、エラー情報を正確に保持しつつ、柔軟なエラーハンドリングが可能になります。


実装手順

1. エラーハンドリング用 error.tsx の作成

Next.js の 公式ドキュメント に基づき、error.tsx を作成します。これにより、サーバーサイドおよびクライアントサイドの想定外エラーをキャッチ可能です。

以下のコードを src/app/error.tsx に追加します:

'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>
      <p>{error.message}</p>
      <button onClick={() => reset()}>再試行</button>
    </div>
  );
}

2. サーバーコンポーネントでのエラー発生

サンプルとして、TodoリストをAPIから取得する機能を作成し、その中で意図的にエラーを発生させます。

page.tsx の実装

import { fetchApi } from '@/app/_utils/fetchApi';
import { TodoList } from '@/components/TodoList';

export default async function Todos() {
  const todos = await fetchApi('https://jsonplaceholder.typicode.com/todos');

  return (
    <div>
      <h1>Todoリスト</h1>
      <TodoList todos={todos} />
    </div>
  );
}

API通信でエラーを発生

fetchApi.ts に意図的にエラーをスローするコードを記述します:

export async function fetchApi(url: string) {
  throw new Error('テストエラーが発生しました');
}

これにより、エラーが error.tsx にキャッチされ、以下のようなエラーメッセージが表示されます:


3. カスタムエラーの導入

APIエラーをわかりやすく伝えるため、以下のようなカスタムエラークラスを作成します。

customError.ts の作成

export type APIErrorObject = {
  name: string;
  message: string;
  title: string; // 表示用タイトル
  description: string; // 詳細メッセージ
};

export class APIError extends Error {
  title: string;
  description: string;

  constructor({ name, message, title, description }: APIErrorObject) {
    super(message);
    this.name = name;
    this.title = title;
    this.description = description;
  }

  serialize() {
    return {
      name: this.name,
      message: this.message,
      title: this.title,
      description: this.description,
    };
  }

  static deserialize(data: APIErrorObject) {
    return new APIError(data);
  }
}

これにより、APIエラーの詳細情報をシリアライズ・デシリアライズできます。


4. クライアントコンポーネントでの再スロー

サーバーコンポーネントから渡されたエラーを再スローするためのコンポーネントを作成します。

DeserializeError.tsx

'use client';

import { APIError } from '@/app/_utils/customError';

export default function DeserializeError({ apiErrorObject }: { apiErrorObject: any }) {
  throw APIError.deserialize(apiErrorObject); // 再スロー
}

5. サーバーコンポーネントの修正

APIError をスローする場合、シリアライズ化して DeserializeError に渡します。

修正後の page.tsx

import { APIError } from '@/app/_utils/customError';
import { fetchApi } from '@/app/_utils/fetchApi';
import DeserializeError from '@/components/DeserializeError';

export default async function Todos() {
  try {
    const todos = await fetchApi('https://jsonplaceholder.typicode.com/todos');
    return (
      <div>
        <h1>Todoリスト</h1>
        <TodoList todos={todos} />
      </div>
    );
  } catch (error) {
    if (error instanceof APIError) {
      return <DeserializeError apiErrorObject={error.serialize()} />;
    }
    throw error;
  }
}

6. エラーUIの改善

カスタムエラー情報を利用して、ユーザー向けの分かりやすいエラー表示を実現します。

error.tsx の修正

import { APIError } from '@/app/_utils/customError';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>{error instanceof APIError ? error.title : 'エラーが発生しました'}</h2>
      <p>{error instanceof APIError ? error.description : error.message}</p>
      <button onClick={() => reset()}>再試行</button>
    </div>
  );
}

まとめ

この手法により、以下を実現できます:

  • サーバーサイドとクライアントサイド間でのエラー情報の適切な受け渡し
  • ユーザーに対するわかりやすいエラー表示
  • デバッグの効率化

Next.js の App Router を活用し、柔軟かつ直感的なエラーハンドリングを実現しましょう!

);

コメント(0件)


トピックス