← 解説「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」をワンセットで覚える。