© TOOLS BOX — Next.js / React / TypeScript コードサンプル集

サンプルガイド
←サンプル一覧
nextjsform

Next.js 15 Server Actions でフォーム送信を実装する

Next.js App Router の Server Actions を使ってフォーム送信を実装する例。useActionState と Zod によるサーバー側バリデーションを示す。

難易度: 中級·更新: 2026-04-14·
zodtailwindcss

対応バージョン

nextjs 15react 19typescript 5zod 3tailwindcss 4

前提環境

Next.js App Router と Server Components の基本を理解していること

概要

Server Actions を使うとフォーム送信を API Route なしでサーバー側で処理できる。 useActionState(旧 useFormState)と組み合わせることで、サーバーエラーをクライアントに返す型安全なパターンを実現する。

Action 定義

// src/app/contact/_actions/submit.ts
"use server";

import { z } from "zod";

const schema = z.object({
  name: z.string().min(1, "名前は必須です"),
  message: z.string().min(10, "メッセージは 10 文字以上で入力してください"),
});

export type ActionState = {
  errors?: Record<string, string[]>;
  success?: boolean;
};

export async function submitContact(
  _prev: ActionState,
  formData: FormData
): Promise<ActionState> {
  const result = schema.safeParse({
    name: formData.get("name"),
    message: formData.get("message"),
  });

  if (!result.success) {
    return { errors: result.error.flatten().fieldErrors };
  }

  // TODO: DB 保存や送信処理
  console.log("Received:", result.data);

  return { success: true };
}

フォームコンポーネント

// src/app/contact/page.tsx
"use client";

import { useActionState } from "react";
import { submitContact, type ActionState } from "./_actions/submit";

const initialState: ActionState = {};

export default function ContactPage() {
  const [state, action, isPending] = useActionState(submitContact, initialState);

  if (state.success) {
    return <p className="text-green-600">送信が完了しました。</p>;
  }

  return (
    <form action={action} className="space-y-4 max-w-sm">
      <div>
        <label htmlFor="name" className="block text-sm font-medium">
          名前
        </label>
        <input
          id="name"
          name="name"
          className="mt-1 block w-full rounded border px-3 py-2 text-sm"
        />
        {state.errors?.name && (
          <p className="mt-1 text-xs text-red-600">{state.errors.name[0]}</p>
        )}
      </div>

      <div>
        <label htmlFor="message" className="block text-sm font-medium">
          メッセージ
        </label>
        <textarea
          id="message"
          name="message"
          rows={4}
          className="mt-1 block w-full rounded border px-3 py-2 text-sm"
        />
        {state.errors?.message && (
          <p className="mt-1 text-xs text-red-600">{state.errors.message[0]}</p>
        )}
      </div>

      <button
        type="submit"
        disabled={isPending}
        className="w-full rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
      >
        {isPending ? "送信中..." : "送信"}
      </button>
    </form>
  );
}

ポイント

  • "use server" ディレクティブで Server Action を定義する
  • useActionState でアクションの状態(エラー / 成功)を管理する
  • Zod の flatten().fieldErrors でフィールド単位のエラーを取得する

関連サンプル

同じテーマや技術スタックを使った実装例

  • Next.js Server Actions でフォーム送信を処理する

    Server Actions と useActionState を使い、クライアントコンポーネントからサーバー関数を直接呼び出してフォーム送信を処理する実装例。

  • Next.js 15 + React Hook Form + Zod でログインフォームを作る

    Next.js App Router 環境で react-hook-form と zod を使ったログインフォームの実装例。クライアントバリデーションとエラー表示を型安全に扱う。

  • React Hook Form の useFieldArray で動的フィールドを追加・削除する

    useFieldArray を使い、フォーム内のフィールドを動的に追加・削除・並び替えする実装例。Zod で配列バリデーションも行う。

  • React Hook Form でファイルアップロード対応フォームを実装する

    React Hook Form の register でファイル入力を管理し、Zod でファイルサイズ・拡張子バリデーションを行う実装例。

  • Zod スキーマをフォームバリデーションと API レスポンス検証で共有する

    同一の Zod スキーマを React Hook Form のバリデーションと fetch レスポンス検証の両方で再利用し、型定義の重複をなくす実装例。

関連仕様

このサンプルを理解するのに役立つ仕様や概念

  • FrameworkNext.jsReact ベースのフルスタックフレームワーク。SSR・SSG・App Router・API Routes を提供する。
  • LibraryZodTypeScript ファーストのスキーマバリデーションライブラリ。型推論と実行時バリデーションを同一スキーマで行える。
←サンプル一覧に戻る