Next.js Server Actionsでのエラーハンドリングと型安全なレスポンス設計
Next.js React TypeScript Backend
結論
予想されるエラーは return 値で表現し、redirect() は try/catch の外側で呼ぶのが公式ルールです。
// Next.js公式仕様:Server Actions でのエラーハンドリング
'use server';
import { redirect } from 'next/navigation';
import { z } from 'zod';
type ActionResult = { success: boolean; error?: string };
export async function createUser(prevState: ActionResult, formData: FormData): Promise<ActionResult> {
let isSuccess = false;
try {
const email = formData.get('email');
if (!email) {
return { success: false, error: 'メールアドレスは必須です' }; // Expected Error
}
// DB登録処理...
isSuccess = true;
} catch (err) {
// Uncaught Exceptions
return { success: false, error: 'システムエラーが発生しました' };
}
// ⭕ redirect() は必ず try/catch の外側で呼び出す
if (isSuccess) {
redirect('/dashboard');
}
}
Next.js公式仕様:2種類のエラー分類
Next.js公式ドキュメント(nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations)では、エラーを2種類に分類してハンドリングすることを推奨しています。
| エラー分類 | 対象例 | ハンドリング手法 |
|---|---|---|
| Expected Errors (予想されるエラー) | フォームバリデーション失敗、パスワード不一致 | throw せず return { error } のオブジェクト値として返却 |
| Uncaught Exceptions (予期せぬ例外) | DB接続切断、サードパーティAPI障害 | try/catch でキャッチするか、error.tsx 境界へ委譲 |
実際に起こる事故:try/catch 内での redirect() 呼び出し罠
Next.jsの redirect() 関数は、内部的に特殊な例外エラー(NEXT_REDIRECT)を throw することでフレームワークのページ遷移を起動させる仕様になっています。
そのため、以下のように try ブロックの中で redirect() を呼び出すと、catch ブロックがリダイレクト用例外を捕獲してしまい、ページ遷移が一切動作しなくなる障害 が発生します。
// ❌ 事故:redirect が catch に捕まりリダイレクトしないダメな例
export async function myAction() {
try {
// 処理...
redirect('/dashboard'); // ❌ 内部で throw される例外が下の catch に入ってしまう!
} catch (err) {
return { error: 'エラー発生' };
}
}
実装手順
- Server Action 内で入力値チェック(Zod等)を行い、不備がある場合は
return { success: false, error: '文言' }を返す - DB更新などの通信処理のみを
try...catchで囲む - 成功後のリダイレクト処理
redirect('/path')は、必ずtry...catchブロックのスコープ外(外側)で実行する - クライアント側(Form)では React 19 の
useActionStateフックを使ってエラーメッセージを表示する