Chapter 13: エラー処理
データの更新ができるようになると,失敗したときの備えが欠かせません。この章では JavaScript の try/catch と Next.js のエラーバウンダリ API を使い,ユーザー体験を損ねないエラーハンドリングを実装します。
この章で扱う内容
- 特別な
error.tsxファイルを使い,ルートセグメント内のエラーを捕捉して,ユーザーにフォールバック UI を表示する方法。 notFound関数 とnot-foundファイル を使い,存在しないリソースに対して 404 を返す方法。
Server Actions に try/catch を追加する
まずは JavaScript の try/catch を Server Actions に追加して,エラーを丁寧に処理できるようにします。自分で書ける場合はそのまま実装して構いません。以下は例です。
export async function createInvoice(formData: FormData) {
const { customerId, amount, status } = CreateInvoice.parse({
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
});
const amountInCents = amount * 100;
const date = new Date().toISOString().split('T')[0];
try {
await sql`
INSERT INTO invoices (customer_id, amount, status, date)
VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
`;
} catch (error) {
// ひとまずコンソールに記録しておく
console.error(error);
return {
message: 'Database Error: Failed to Create Invoice.',
};
}
revalidatePath('/dashboard/invoices');
redirect('/dashboard/invoices');
}
export async function updateInvoice(id: string, formData: FormData) {
const { customerId, amount, status } = UpdateInvoice.parse({
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
});
const amountInCents = amount * 100;
try {
await sql`
UPDATE invoices
SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status}
WHERE id = ${id}
`;
} catch (error) {
// ひとまずコンソールに記録しておく
console.error(error);
return { message: 'Database Error: Failed to Update Invoice.' };
}
revalidatePath('/dashboard/invoices');
redirect('/dashboard/invoices');
}
redirect は try/catch の外で呼んでいる点に注意してください。redirect は内部的に例外を投げて遷移を行うため,catch 節で捕捉されないようにする必要があります。try が成功した場合のみ到達する位置に置きます。
ここでは,DB 側の問題を 捕捉してユーザーに分かりやすいメッセージを返す という,グレースフルなエラー処理にしています。
未捕捉の例外が発生したらどうなるでしょうか。deleteInvoice アクションで明示的にエラーを投げてシミュレートしてみます。
export async function deleteInvoice(id: string) {
throw new Error('Failed to Delete Invoice');
// 到達しないコード
await sql`DELETE FROM invoices WHERE id = ${id}`;
revalidatePath('/dashboard/invoices');
}
この状態で請求書の削除を試すと,ローカルホストにエラーが表示されます。本番環境では,予期しないエラーが起きたときにもユーザーに丁寧な UI を見せたいはずです。そこで error.tsx を使います。なお,このテスト用の throw は次のステップに進む前に必ず削除してください。
error.tsx でルート配下の未捕捉エラーを扱う
error.tsx はルートセグメント用の UI 境界 を定義する特別なファイルです。予期しないエラーをキャッチし,ユーザーにフォールバック UI を表示できます。
/dashboard/invoices フォルダに 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 (
<main className="flex h-full flex-col items-center justify-center">
<h2 className="text-center">Something went wrong!</h2>
<button
className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400"
onClick={
// エラーバウンダリをリセットしてルートを再レンダリング
() => reset()
}
>
Try again
</button>
</main>
);
}
ポイント:
- 先頭に
"use client"が必要です(クライアントコンポーネント)。 errorはネイティブのErrorインスタンス,resetは エラーバウンダリをリセットして再レンダリングを試みる関数です。
これで再度削除を試すと,フォールバック UI が表示されます。

notFound と not-found.tsx による 404 の扱い
error.tsx は未捕捉例外のキャッチに有用ですが,存在しないリソースを扱うときは notFound を使って 404 を明示するのが適切です。
例として,以下の URL にアクセスしてみます(存在しない UUID):
今は /invoices 配下に error.tsx があるため,この子ルートでエラー UI が表示されます。しかし,ここは 404 を返すのが望ましいケースです。
data.ts の fetchInvoiceById で戻り値をログすると,存在しない場合は空配列であることが分かります。
export async function fetchInvoiceById(id: string) {
try {
// ...
console.log(invoice); // [] が返る
return invoice[0];
} catch (error) {
console.error('Database Error:', error);
throw new Error('Failed to fetch invoice.');
}
}
/dashboard/invoices/[id]/edit/page.tsx で notFound を使いましょう。
import { fetchInvoiceById, fetchCustomers } from '@/app/lib/data';
import { notFound } from 'next/navigation';
export default async function Page(props: { params: Promise<{ id: string }> }) {
const params = await props.params;
const id = params.id;
const [invoice, customers] = await Promise.all([
fetchInvoiceById(id),
fetchCustomers(),
]);
if (!invoice) {
notFound();
}
// ...
}
そして,ユーザーに見せる 404 UI を not-found.tsx で定義します(/edit フォルダ内)。

import Link from 'next/link';
import { FaceFrownIcon } from '@heroicons/react/24/outline';
export default function NotFound() {
return (
<main className="flex h-full flex-col items-center justify-center gap-2">
<FaceFrownIcon className="w-10 text-gray-400" />
<h2 className="text-xl font-semibold">404 Not Found</h2>
<p>Could not find the requested invoice.</p>
<Link
href="/dashboard/invoices"
className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400"
>
Go Back
</Link>
</main>
);
}
リロードすると 404 ページが表示されるはずです。

覚えておきたいのは,notFound() が error.tsx より優先される点です。より 特定の状況を明示したいときに notFound() を使うとよいでしょう。