Next.js appRouter エラーハンドリングについて
公開日時:2024/09/15
error.js(公式)
appRouterにおいて、error.jsを作成することでツリー内でthrowされたErrorを拾ってエラーページをハンドリングできる。
しかしdev環境とbuild環境では挙動(Errorの扱い)が異なる。
結論だけ言うと、build環境では error message が上書きされてしまうのだ。
error.jsを作成する
app/error.tsxを作成する。単純にerror.messageとerror.digestを表示するだけのコンポーネントにする。
digestはappRouterでthrowされるErrorが自動的に拡張しているものになる。
'use client';
export default function GlobalError({
error,
}: {
error: Error & { digest: string };
}) {
return (
<html lang='ja'>
<body>
<h1>Error</h1>
<pre>{error.message}</pre>
<pre>{error.digest}</pre>
</body>
</html>
);
}
CustomErrorを作成する
通常のErrorは throw new Error('error')
のように messageを引数にとる。
先述したようにdigestを拡張したErrorを CustomErrorとして定義する。この理由については後述する。
export class CustomError extends Error {
digest: string;
constructor(message: string, digest?: string) {
super(message);
this.digest = digest || 'digest';
}
}
dynamic routeでCustomErrorをthrowするpageを作成する
例えば、dynamic routeでpageの[id]が4桁ではない場合に400エラーにするようなエラーハンドリングを想定する。
この時に取れる方法は2つ。
- page.tsx内で400エラー用のコンポーネントをreturnする。
- error.jsでハンドリングする。(注意)
2についての挙動がこの記事のメイン。
まずは分岐を実装する。
messageは custom Error
digestは code=400
とする。
import { CustomError } from '../_customError';
export default async function Page({ params }: { params: { id: string } }) {
if (!params.id.match(/\d{4}/)) {
throw new CustomError('custom error', 'code=400');
}
return (
<div>
<h2>{params.id} page</h2>
</div>
);
}
この時にErrorをthrowした場合の挙動が dev環境と build環境で異なる。
dev環境
messageは custom Error
digestは code=400
実装通りのものがthrowされていることがわかる

build環境
digestは code=400
で変わらないが
messageは上書きされてNext.jsが設定したエラーメッセージが表示されてしまっている。

もし、次のように error.js内で error.message で分岐を実装していた場合、dev環境とbuild環境で結果が変わってしまう。
'use client';
export default function GlobalError({
error,
}: {
error: Error & { digest: string };
}) {
if (error.message === 'custom error') {
return (
<html lang='ja'>
<body>
<h1>Custom Error</h1>
<pre>{error.message}</pre>
<pre>{error.digest}</pre>
</body>
</html>
);
}
return (
<html lang='ja'>
<body>
<h1>Error</h1>
<pre>{error.message}</pre>
<pre>{error.digest}</pre>
</body>
</html>
);
その対策で、error.digestで分岐をするのが良さそう。
error.js error.digestで分岐を実装したサンプル
'use client';
export default function GlobalError({
error,
}: {
error: Error & { digest: string };
}) {
if (error.digest === 'code=400') {
return (
<html lang='ja'>
<body>
<h1>Bad Request</h1>
</body>
</html>
);
}
return (
<html lang='ja'>
<body>
<h1>Error</h1>
<pre>{error.message}</pre>
<pre>{error.digest}</pre>
</body>
</html>
);
}