blog-hero-img

Server Actionsのエラー処理【2つの方法を紹介/Next.js】

pen-icon2024.10.21

この記事は約3分で読めます

Profile Pic

この記事の筆者:三好アキ


🔹 専門用語なしでプログラミングを教えるメソッドに定評があり、1200人以上のビギナーを、最新のフロントエンド開発入門に成功させる。

🔹 Amazonベストセラー1位を複数回獲得している『はじめてつくるReactアプリ with TypeScript』著者。


Amazon著者ページはこちら → amazon.co.jp/stores/author/B099Z51QF2



React、Next.js、TypeScriptなどのお役立ち情報や実践的コンテンツを、ビギナー向けにかみ砕いて無料配信中。登録はこちらから → 無料メルマガ登録

Server Actionsのエラー処理方法

Next.jsのServer Actionsでは、returnしたvalue(戻り値)の扱い方にややクセがあります。

たとえば下記のようなServer Actionsのコードを考えてみましょう。

フロントエンドのユーザー登録画面に入力された「名前」「メールアドレス」「パスワード」を、データベースに書き込むServer Actionsのコードです。

"use server"
import { redirect } from "next/navigation"
import connectDB from "../utils/database"
import { UserModel } from "../utils/schemaModels"

export const userRegister = async (formData) => {

    const userData = {
        name: formData.get("name"),
        email: formData.get("email"),
        password: formData.get("password"),
    }

    try{
        await connectDB() 
        await UserModel.create(userData) 
    }catch{
        return {message: "エラー:ユーザー登録失敗"}
    } 

    redirect("/")
}

このコードの挙動を説明しましょう。

tryの中のコードでデータベースへの書き込みが成功すれば、redirect("/")でトップページへと移動しますが、もし途中でなんらかのエラーが発生した場合にはcatch内のコードが実行される、という挙動です。

ここで、このエラーメッセージをどのようにフロントエンド側に表示するかで、方法が大きく2つあります。

1つ目はerror.jsを使う方法、2つ目はReact Hooks(useFormState、あるいはuseActionState)を使う方法です。

error.jsを使ったエラーハンドリング

error.jsを、appフォルダ直下(layout.jspage.jsのある階層)に作り、下記のようなコードを書きます。

// error.js

"use client"
import { useEffect } from "react"

const Error = ({ error, reset }) => {
    return (
        <div>
            <h2>{error.message}</h2>
            <button onClick={() => reset()}>再試行</button>
        </div>
    )
}

export default Error

error.jsは、設置した階層以下で発生したエラーをキャッチする機能を持ちます。

注意してもらいたいのは、error.jsはクライアントコンポーネントにする必要があることです。"use client"を忘れないようにしましょう。

次に、本記事冒頭のServer Actionsのcatch内のコードを、エラー発生を通知する下記コードに変更します。


    ...

    try{
        await connectDB() 
        await UserModel.create(userData) 
    }catch{
        throw new Error("エラー:ユーザー登録失敗")  // 変更
    } 

    redirect("/")
}

Error()の中に書かれた言葉は、error.jsに渡されたerrorの中に入っているので、そこへアクセスをするために<h2>タグの中でerror.messageと書いています。

なおerror.jsでは、resetという特別なfunctionが利用可能です。

以上がerror.jsのエラーハンドリングで、次に紹介するのがHooksを使った方法です。

Hooksを使ったエラーハンドリング

React Hooks、具体的にはuseFormStateを使います

Hooksを使うので、フロントエンド側のコンポーネントは、サーバーコンポーネントではなくクライアントコンポーネントにすることに注意しましょう。

"use client"
import { useFormState } from "react"
import { userRegister } from "@/app/actions/userRegister"

const initialState = {
    message: "",
}

const Register = () => {
    const [state, formAction] = useFormState(userRegister, initialState)
    return (
        <div>
            <h1>ユーザー登録</h1>
            <form action={formAction}>
                <input type="text" name="name" placeholder="名前" required/> 
                <input type="text" name="email" placeholder="メールアドレス" required/>
                <input type="text" name="password" placeholder="パスワード" required/>
                <p>{state?.message}</p>
                <button>登録</button>
            </form> 
        </div>
    )
}

export default Register

この方法では、Server Actionsのcatch内ではErrorを使う必要はなく、本記事冒頭で示したreturnを使ったコードで大丈夫です。

"use server"
import { redirect } from "next/navigation"
import connectDB from "../utils/database"
import { UserModel } from "../utils/schemaModels"

export const userRegister = async (prevState, formData) => {

    const userData = {
        name: formData.get("name"),
        email: formData.get("email"),
        password: formData.get("password"),
    }

    try{
        await connectDB() 
        await UserModel.create(userData) 
    }catch{
        return {message: "エラー:ユーザー登録失敗"}
    } 

    redirect("/")
}

ここで注目して欲しいのが、Server ActionsにformDataだけでなくprevStateも渡されていることです。

このようにすることで、フロントエンドのstateの中に、catch内に書かれたmessageが渡されます。


なお、Reactバージョン19ではuseFormStateuseActionStateとなり、この場合は次のように書きます。

useActionStateでは新たにisPendingが利用可能なので、<button>タグの中に処理中であることを知らせるコードを追加しています。

"use client"
import { useActionState } from "react"
import { userRegister } from "@/app/actions/userRegister"

const initialState = {
    message: "",
}

const Register = () => {
    const [state, formAction, isPending] = useActionState(userRegister, initialState)
    return (
        <div>
            <h1>ユーザー登録</h1>
            <form action={formAction}>
                <input type="text" name="name" placeholder="名前" required/> 
                <input type="text" name="email" placeholder="メールアドレス" required/>
                <input type="text" name="password" placeholder="パスワード" required/>
                <p>{state?.message}</p>
                <button>{isPending ? "処理中..." : "登録"}</button>
            </form> 
        </div>
    )
}

export default Register

使い分け

本記事ではerror.jsとHooks(useFormState、あるいはuseActionState)を使う方法を紹介してきましたが、使い分けになにか指標はあるのでしょうか。

Next.js公式では、「起きることが予期できているエラー」にはHooksを、「起きることが予期できていないエラー」にはerror.jsを使う方法が紹介されています。

「起きることが予期できているエラー」とは、たとえばログイン機能を担うServer Actionsなどで、「ログインの前にユーザー登録をしてください」、「パスワードが間違っています」といった場合分けのエラー対応を行うケースです。

throw new Error()でエラーを発生させてerror.jsで表示する」というのは、より一般的で守備範囲の広い対応方法ということになります。

Profile Pic

メルマガ配信中
(from 三好アキ/エンジニア)


React、Next.js、TypeScriptなど最新のウェブ開発のお役立ち情報を、ビギナー向けにかみ砕いて無料配信中。
(*配信はいつでも停止できます)