0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js】error.tsxを使ったエラーハンドリングでハマった

Last updated at Posted at 2025-01-03

やろうとしていたこと

サーバーサイドの処理で、エラーが発生した場合、エラーのステータスに応じて、エラー画面を出し分ける。
https://nextjs.org/docs/app/building-your-application/routing/error-handling

環境

"next": "14.2.11",
"react": "18.3.1",
"typescript": "5.6.3"

※App router使用

起きていた事象

最初は、こんな感じで実装していました。
devモードではこれで問題なくステータスによって出し分けできていましたが、prodモードで確認したところ、うまく動作しませんでした。( if (error instanceof CustomError)に不一致でswitch文に流れていなかった。)

//src/app/error.tsx
"use client";

export default function ErrorPage({ error }: { error: CustomError }) {
  if (error instanceof CustomError) {
    switch (error.statusCode) {
      case 400:
        return <p> Bad Request</p>;
      case 500:
        return <p>Internal Server Error</p>;
      case 503:
        return <p>Service Unavailable</p>;
      default:
        return <p>Error</p>;
    }
  }
  return <p>error</p>;
}
//src/app/custom-error.ts
import { CustomError } from "./custom-error";

export class CustomError extends Error {
  statusCode: number;
  constructor(error: { statusCode: number; message: string }) {
    super();
    this.statusCode = error.statusCode;
    this.message = error.message;
  }
}
//src/app/page.tsx
import { CustomError } from "./error";

export default function Home() {
  throw new CustomError({
    message: "Service Unavailable",
    statusCode: 503,
  });
}

原因

prodモードでerror.tsxに渡されたerrorをデバッグしてみると、このような文字列になっていました。

Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.

※訳エラーが発生しました: Server Components のレンダーでエラーが発生しました。具体的なメッセージは、機密情報の漏えいを防ぐため、プロダクションビルドでは省略されます。このエラーインスタンスにはダイジェストプロパティが含まれています。
との内容で、devモードでは問題ないですが、エラーオブジェクトをclient sideに出すと情報漏洩の観点でまずいので、そういった仕様になっているようです。

解決方法

サーバー側からErrorオブジェクトしては直接client sideに渡すことはできないので、下記の手順でerror.tsxまでエラーを渡します。

  1. server sideのエラーをserver sideで捕捉(catch)
  2. 捕捉したエラーを普通のオブジェクトに変換
  3. 変換したオブジェクトをclient side(component)に渡す
  4. client sideでエラーを元の型(カスタムエラー)に戻して再度throwする
  5. error.tsxはclient side renderingなので、問題なくカスタムエラーを受け取れる
//src/app/custom-error.ts
export type APIErrorObject = {
  message: string;
  errorCode: number;
};

export class CustomError extends Error {
  errorCode: number;
  constructor(props: APIErrorObject) {
    super(props.message);
    this.errorCode = props.errorCode;
  }
  serialize(): SerializedCustomError {
    return {
      message: this.message,
      errorCode: this.errorCode,
    };
  }
  static deserialize(data: SerializedCustomError) {
    return new CustomError({
      message: data.message,
      errorCode: data.errorCode,
    });
  }
}

export type SerializedCustomError = {
  message: string;
  errorCode: number;
};
// src/app/page.tsx
import { ThrowDeserializedCustomError } from "@/client-components/client-component";
import { CustomError } from "./custom-error";

export default function Home() {
  try {
    // なんらかの処理で例外発生
    throw new CustomError({
      message: "Service Unavailable",
      errorCode: 400,
    });
  } catch (e) {
    // サーバーサイドで捕捉
    if (e instanceof CustomError) {
      // ThrowDeserializedCustomErrorはclient component
      // ここにserializedされたエラーオブジェクトを渡す
      return <ThrowDeserializedCustomError serializedError={e.serialize()} />;
    }
  }
}
// src/client-components/client-component.tsx
"use client";

import { SerializedCustomError } from "@/app/custom-error";
import { CustomError } from "../app/custom-error";
export type Props = {
  serializedError: SerializedCustomError;
};

export const ThrowDeserializedCustomError = ({ serializedError }: Props) => {
  // client sideから再度throwする
  throw CustomError.deserialize(serializedError);
};
// src/app/error.tsx
"use client";

import { CustomError } from "./custom-error";

export default function ErrorPage({ error }: { error: CustomError }) {
  // client side(ThrowDeserializedCustomError)からthrowされたエラーが渡される
  if (error instanceof CustomError) {
    switch (error.errorCode) {
      case 400:
        return <p> Bad Request</p>;
      case 500:
        return <p>Internal Server Error</p>;
      case 503:
        return <p>Service Unavailable</p>;
      default:
        return <p>Error</p>;
    }
  }
  return <p>error</p>;
}

参考

https://zenn.dev/nabeliwo/articles/02f8cfcc596bb9
https://qiita.com/__ttsubasa__/items/a1fd399dd490e7402518
https://zenn.dev/link/comments/8a78d3c89fdabe
https://dev-harry-next.com/frontend/nextjs-app-router-error-handling#hf07ff9d3b8

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?