1. ホーム
  2. Next.js

【Next.js】Server Actions でフォーム送信・データ更新を行う方法|”use server” の使い方を解説

Share

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={...}> に渡します。

app/page.tsx
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("キー名") を使います。

app/actions.ts
"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 を使います。

app/actions.ts
"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 というフックで、フォームが送信中かどうかを取得できます。送信ボタンを送信中だけ無効化する、といった表現に使えます。

app/submit-button.tsx
"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 を活用してみてください。

参考ページ