Next.js の App Router には、フォームの送信やデータの更新を、サーバー側で直接処理できる Server Actions(サーバーアクション)という仕組みがあります。これは「サーバー上で実行される非同期関数」で、これまで別途用意していたAPI(データを受け取るURL)を作らなくても、関数をそのままフォームに紐付けて動かせるのが特徴です。この記事では、"use server" というディレクティブの書き方から、formData での値の受け取り、更新後の画面反映や画面遷移、Client Component からの呼び出しまで、初心者〜中級者向けにコード付きで解説します。
目次
Server Actions とは
Server Actions は、その名のとおり「サーバー上で実行される非同期関数」です。関数の先頭やファイルの先頭に "use server" と書くことで、その関数は必ずサーバー側で動くようになります。ポイントは、この関数を <form> の action 属性に直接渡せることです。フォームが送信されると、Next.js が自動的にサーバー側でその関数を呼び出してくれます。API用のURL(エンドポイント)を自分で用意する必要はありません。
サーバー側で動くということは、データベースへの接続情報やAPIキーといった機密情報をそのまま書けるということでもあります。これらのコードはブラウザには送られないため、外部から見られる心配がありません。フォームの送信先を作るときの選択肢が、ぐっとシンプルになります。
基本の書き方:フォームに関数を渡す
まずは一番シンプルな例を見てみましょう。Server Component(App Router で既定のコンポーネント)の中に "use server" を先頭に書いた関数を用意し、それを <form action={...}> に渡します。
export default function Page() {
// フォーム送信時にサーバーで実行される関数
async function createTodo(formData: FormData) {
"use server";
const title = formData.get("title");
console.log("サーバー側で受け取った値:", title);
// ここでデータベースに保存する、などの処理を行う
}
return (
<form action={createTodo}>
<input type="text" name="title" />
<button type="submit">追加</button>
</form>
);
}
関数の先頭に書いた "use server" が、この createTodo を Server Action にしています。<form> の action 属性にこの関数を渡すと、ユーザーが送信ボタンを押したときに、Next.js が自動でサーバー側の createTodo を呼び出します。このとき、フォームの入力内容が FormData というオブジェクトとして関数の引数に渡されます。
formData から値を取り出す
引数で受け取る formData は、ブラウザ標準の FormData オブジェクトです。フォーム内の各入力欄は、その name 属性をキーにして値を持っています。値を取り出すには formData.get("キー名") を使います。
"use server";
export async function createUser(formData: FormData) {
// input の name 属性をキーにして値を取り出す
const name = formData.get("name");
const email = formData.get("email");
// get() の戻り値は string | File | null なので、文字列に変換して扱う
const nameText = String(name ?? "");
console.log(nameText, email);
}
ここではファイルの先頭に "use server" を書いています。こうすると、そのファイルで export した関数はすべて Server Action になります。複数の Server Action をまとめて置きたいときは、この「ファイル先頭に書く」方法が便利です。formData.get() の戻り値は、値が無ければ null、ファイル入力なら File になるため、テキストとして扱うときは String(...) などで変換しておくと安全です。
更新後に画面へ反映する:revalidatePath と redirect
データを保存したあとは、その結果を画面に反映させたいところです。Next.js はページの内容をキャッシュ(一時保存)して高速に表示するため、データを更新しただけでは古い表示のままになることがあります。そこで revalidatePath を使い、「このパスのキャッシュを作り直して」と指示します。また、更新後に別のページへ移動させたいときは redirect を使います。
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export async function createPost(formData: FormData) {
const title = String(formData.get("title") ?? "");
// ここでデータベースに保存する処理を行う
// await db.post.create({ data: { title } });
// 一覧ページのキャッシュを作り直して、新しいデータを反映させる
revalidatePath("/posts");
// 詳細ページへ遷移する
redirect("/posts");
}
revalidatePath("/posts") は、/posts ページのキャッシュを破棄して次のアクセス時に最新データで再生成させます。これで、追加した投稿が一覧にすぐ表示されます。redirect("/posts") は、処理が終わったあとにユーザーを /posts へ移動させます。redirect は内部的にエラーを投げて処理を中断する仕組みのため、原則として関数の最後(try/catch の外)で呼ぶのがポイントです。
Client Component から呼び出す
Server Action は、"use client" を付けた Client Component からも使えます。別ファイルに定義した Server Action を import して、同じように <form action={...}> に渡すだけです。ここで役立つのが useFormStatus というフックで、フォームが送信中かどうかを取得できます。送信ボタンを送信中だけ無効化する、といった表現に使えます。
"use client";
import { useFormStatus } from "react-dom";
export function SubmitButton() {
// pending が true の間は送信処理中
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "送信中..." : "送信"}
</button>
);
}
useFormStatus は、それを使うコンポーネントが <form> の内側にあるときに機能します。そのため、上のように送信ボタンだけを独立した Client Component に切り出し、フォームの中に置いて使います。この SubmitButton を、Server Action を action に渡したフォームの中に配置すれば、送信中は自動的にボタンが「送信中…」に変わります。
Route Handler と Server Actions の違い
App Router には、サーバー側の処理を書く方法として route.ts に関数を書く Route Handler もあります。両者は似ていますが、役割が異なります。Route Handler は「アクセスできるURL(APIエンドポイント)」を明示的に作る方法で、Server Actions は「関数を直接フォームに紐付ける」方法です。使い分けの目安を表にまとめます。
| 観点 | Route Handler(route.ts) | Server Actions |
|---|---|---|
| 作るもの | URL(APIエンドポイント) | フォームに紐付ける関数 |
| 呼び出し方 | fetch でURLにアクセス | <form action={fn}> に渡す |
| URLの有無 | あり(外部からも叩ける) | なし(フォーム経由で実行) |
| 向いている用途 | 外部公開API・Webhook受け口 | アプリ内のフォーム送信・データ更新 |
| JS無効でも動くか | 動かない(fetch が必要) | 動く(フォーム送信で完結) |
ざっくり言えば、スマホアプリや外部サービスからも呼びたい「公開API」なら Route Handler、自分のアプリ内のフォーム送信やデータ更新なら Server Actions が向いています。両方を組み合わせて使っても構いません。
JavaScript が無効でも動く強み
Server Actions のもうひとつの利点が、progressive enhancement(プログレッシブエンハンスメント)への対応です。これは「まず基本機能が動き、環境が整えばより快適になる」という考え方です。Server Actions を <form> に渡した場合、ブラウザの JavaScript が無効でも、通常のフォーム送信としてサーバーに届き、処理が実行されます。JavaScript が有効なら、ページ全体を再読み込みせずに更新できます。fetch に頼る通常のAPI呼び出しでは、JavaScript が無効だと何も動きませんが、Server Actions はフォーム送信の仕組みそのものを利用するため、こうした堅牢さが得られます。
まとめ
Server Actions は、関数の先頭またはファイル先頭に "use server" と書くだけで作れる、サーバー側で実行される非同期関数です。<form action={fn}> に渡せば、API用のURLを別途作らなくてもフォーム送信やデータ更新を処理できます。フォームの値は引数の formData から formData.get("name") で取り出し、更新後は revalidatePath で画面に反映、redirect で画面遷移を行います。Client Component からは import して呼び出せ、useFormStatus で送信中の状態も扱えます。明示的なURLが必要なら Route Handler、アプリ内のフォーム処理なら Server Actions、と使い分けて App Router を活用してみてください。