Dev Study
← 解説「Server Actions — フォームからサーバー処理を呼ぶ」に戻る

サンプルコードで身につける: Server Actions — フォームからサーバー処理を呼ぶ

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1最小の Server Action

フォームの中に "use server" の関数を書き、action に渡すだけの最小形です。送信ボタンを押すとこの関数がサーバーで実行される、という1ファイル完結の流れをまず体に入れましょう。

// app/feedback/page.tsx
async function submitFeedback(formData: FormData) {
  "use server"; // この関数はサーバーで実行される
  const feedback = formData.get("feedback");
  console.log("受信:", feedback); // ターミナルに出る
}

export default function FeedbackPage() {
  return (
    <form action={submitFeedback}>
      <input name="feedback" placeholder="ご意見をどうぞ" />
      <button type="submit">送信</button>
    </form>
  );
}

2actions.ts に分離する

ファイルの先頭に "use server" を書くと、そのファイルの全関数が Server Action になります。実務ではアクションを actions.ts にまとめて複数のページやコンポーネントから import する、この分離スタイルが標準です。

// app/tasks/actions.ts — ファイル先頭の宣言で全関数が対象に
"use server";

export async function createTask(formData: FormData) {
  const title = formData.get("title");
  console.log("タスク作成:", title);
}

export async function deleteTask(formData: FormData) {
  const id = formData.get("id");
  console.log("タスク削除:", id);
}

// ─────────────────────────────
// app/tasks/page.tsx — import して使う
import { createTask } from "./actions";

export default function TasksPage() {
  return (
    <form action={createTask}>
      <input name="title" />
      <button type="submit">追加</button>
    </form>
  );
}

3複数フィールドの受け取りと検証

input の name 属性が FormData のキーになる、という対応関係を問い合わせフォームで確かめる例です。受け取った値は必ず検証してから使う、というサーバー側処理の鉄則もここで身につけます。

// app/contact/page.tsx — name 属性 = FormData のキー
async function sendInquiry(formData: FormData) {
  "use server";
  const customerName = String(formData.get("customerName") ?? "");
  const email = String(formData.get("email") ?? "");
  const message = String(formData.get("message") ?? "");

  // サーバー側での検証は必須(ブラウザ側の検証は迂回できるため)
  if (customerName === "" || !email.includes("@")) {
    console.log("入力エラー");
    return;
  }
  console.log("問い合わせ受付:", customerName, email, message);
}

export default function ContactPage() {
  return (
    <form action={sendInquiry}>
      <input name="customerName" placeholder="お名前" />
      <input name="email" placeholder="メールアドレス" />
      <textarea name="message" placeholder="お問い合わせ内容" />
      <button type="submit">送信</button>
    </form>
  );
}

4FormData 的な処理を再現する

FormData の正体は「name 属性 → 入力値」の対応表です。それを Map で再現し、取り出し・必須チェック・保存メッセージ生成までのサーバー側処理の流れを、Next.js なしで実行して確かめる例です。

TypeScript

5保存して一覧を更新する

データを保存した後、revalidatePath でそのページのキャッシュを破棄して一覧を最新化する実務の定番形です。「Action で書き込み、revalidate で表示を追いつかせる」という更新系処理のワンセットを覚えると、CRUD 画面が一通り作れます。

// app/tasks/actions.ts
"use server";

import { revalidatePath } from "next/cache";

export async function createTask(formData: FormData) {
  const title = String(formData.get("title") ?? "");
  if (title.trim() === "") return;

  await saveTaskToDb(title); // データベースに保存

  // /tasks のキャッシュを破棄 → 次の表示で最新の一覧になる
  revalidatePath("/tasks");
}

declare function saveTaskToDb(title: string): Promise<void>;

// 保存しただけでは画面のキャッシュは古いまま。
// 「書き込んだら revalidate」をワンセットで覚える。