Next.jsのApp Routerでは、Server Actions(Server Functions)を利用することで、フォーム送信やデータ更新などの処理をサーバー側で実行できます。
従来のReactアプリケーションでは、フォームからAPIへリクエストを送信し、サーバー側でデータを処理する構成が一般的でした。
一方、Next.jsではServer Functionsを利用することで、フォームとサーバー処理をより簡潔に接続できます。
ただし、実装が簡単になる一方で、入力値の検証や認可処理を省略してよいわけではありません。
本記事では、Next.js App Routerを利用したフォーム処理の基本と、安全な実装方法を紹介します。
1. Server Actionsとは?
Server Actionsは、サーバー側で実行される非同期関数をフォーム送信などから呼び出す仕組みです。
例えば、ユーザーがフォームへ入力した情報をサーバーで処理する場合、次のような流れになります。
User
↓
HTML Form
↓
Server Action
↓
Validation
↓
Database / Business Logic
↓
Response
この構成では、クライアント側にデータベース接続情報などを公開せずに処理できます。
ただし、Server Action自体はクライアントから呼び出せるサーバー側の入口になるため、認証・認可の確認が必要です。
2. Next.jsプロジェクトを準備する
まず、新しいNext.jsプロジェクトを作成します。
npx create-next-app@latest next-form-demo
作成時には、TypeScriptとApp Routerを有効にします。
プロジェクトへ移動します。
cd next-form-demo
今回はフォームのバリデーションにZodを利用します。
npm install zod
Zodを利用すると、サーバー側で入力データの形式を検証できます。
3. 基本的なServer Actionを作成する
まず、次のファイルを作成します。
app/actions.ts
"use server";
export async function submitMessage(
formData: FormData
) {
const name = formData.get("name");
console.log("Received:", name);
return {
success: true
};
}
"use server"を指定することで、このファイルからエクスポートされる非同期関数をサーバー側で実行できます。
続いてフォームを作成します。
app/page.tsx
import { submitMessage } from "./actions";
export default function Home() {
return (
<main>
<h1>Contact Form</h1>
<form action={submitMessage}>
<input
type="text"
name="name"
placeholder="Your name"
required
/>
<button type="submit">
Send
</button>
</form>
</main>
);
}
このように、フォームのaction属性へServer Actionを指定できます。
ただし、現時点では入力値を検証していないため、本番環境で利用するには追加の処理が必要です。
4. Zodで入力値を検証する
Webアプリケーションでは、クライアント側の入力制限だけでは不十分です。
HTMLのrequired属性などはユーザー体験の改善には役立ちますが、サーバー側の検証を代替するものではありません。
Zodを利用して入力スキーマを定義します。
import { z } from "zod";
export const messageSchema = z.object({
name: z.string().trim().min(1).max(50),
email: z.email(),
message: z.string().trim().min(1).max(500)
});
この例では、名前、メールアドレス、メッセージに制限を設定しています。
次に、Server Actionへ適用します。
"use server";
import { messageSchema } from "./schema";
export async function submitMessage(
formData: FormData
) {
const result = messageSchema.safeParse({
name: formData.get("name"),
email: formData.get("email"),
message: formData.get("message")
});
if (!result.success) {
return {
success: false,
message: "Invalid input"
};
}
return {
success: true,
message: "Message received"
};
}
safeParse()を利用すると、検証結果を例外ではなくオブジェクトとして取得できます。
これにより、入力エラーを適切に処理できます。
5. 認証と認可を忘れない
Server Actionsを利用する場合、特に重要なのが認証と認可です。
例えば、ユーザー情報を変更する処理では、ログイン状態を確認する必要があります。
概念的には次のような構成です。
"use server";
import { auth } from "@/lib/auth";
export async function updateProfile(
formData: FormData
) {
const session = await auth();
if (!session?.user?.id) {
throw new Error("Unauthorized");
}
// 入力値を検証する
// 対象リソースへの更新権限を確認する
// 許可されたデータのみ更新する
}
ここでのauth()は、利用する認証ライブラリに応じて実装する関数です。
重要なのは、フォームから送信されたユーザーIDをそのまま信用しないことです。
ユーザーの識別情報は、サーバー側で検証済みのセッションから取得する方が安全です。
また、認証済みであることと、特定のデータを更新する権限があることは別問題です。
6. Pending状態を表示する
フォーム送信中に何も変化がないと、ユーザーは処理が実行されているのか判断できません。
ReactのuseFormStatusを利用すると、フォーム送信中の状態を取得できます。
app/submit-button.tsx
"use client";
import { useFormStatus } from "react-dom";
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
>
{pending ? "Sending..." : "Send"}
</button>
);
}
フォーム内で利用します。
<form action={submitMessage}>
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required />
<SubmitButton />
</form>
これにより、送信中の状態をユーザーへ伝えられます。
なお、ボタンを無効化するだけでは重複リクエストを完全に防げません。
重要な更新処理では、サーバー側でも冪等性や重複処理への対策を検討する必要があります。
7. データベース更新時の注意点
Server Actionsはデータベース更新処理にも利用できます。
ただし、データベースへアクセスする場合は、以下の点を確認します。
- 入力値を検証する
- 更新対象へのアクセス権限を確認する
- パラメータ化されたクエリを利用する
- エラー情報を適切に処理する
- 必要に応じてトランザクションを利用する
特に複数のテーブルを同時に更新する場合は、途中で処理が失敗したときの整合性を考慮する必要があります。
Server Actionsは便利な実装手段ですが、データベース設計の基本原則が不要になるわけではありません。
8. キャッシュと画面更新を考える
フォーム送信後にデータが更新された場合、画面へ最新情報を反映する必要があります。
Next.jsでは、利用しているキャッシュ方式に応じてrevalidatePath()などを利用できます。
"use server";
import { revalidatePath } from "next/cache";
export async function updateContent() {
// 認証・認可・入力検証
// データベース更新処理
revalidatePath("/dashboard");
}
この例では、更新処理の成功後に指定したパスのキャッシュを再検証します。
ただし、すべてのデータ取得処理が自動的にキャッシュされるわけではありません。
利用中のNext.jsのバージョンやキャッシュ設定を確認することが重要です。
9. デジタルプラットフォームでの活用
モバイルユーザーを意識したWebサービスでは、フォーム操作の簡潔さと応答性が重要です。
例えば、WOW88 のようなデジタルプラットフォームを技術的な設計例として考える場合、問い合わせフォーム、プロフィール設定、コンテンツ検索条件の保存などにServer Actionsの考え方を応用できます。
特に、フォーム送信中の状態表示、入力エラーの明確化、サーバー側での検証を組み合わせることで、操作性と安全性の両方を考慮した設計が可能になります。
ただし、すべての処理をServer Actionsへ集約する必要はありません。
外部サービス向けAPIやWebhookなどでは、Route Handlersを利用する方が適切な場合もあります。
10. 実装時のチェックリスト
Server Actionsを本番環境で利用する前に、次の項目を確認すると便利です。
□ Server Actionで入力値を検証している
□ 認証が必要な処理でセッションを確認している
□ リソース単位で認可を実施している
□ 機密情報をクライアントへ公開していない
□ エラー内容を適切に処理している
□ フォーム送信中の状態を表示している
□ 重複送信への対策を検討している
□ 更新後のキャッシュ動作を確認している
□ データベース操作の整合性を確認している
このようなチェックリストをコードレビューに取り入れることで、実装上の問題を発見しやすくなります。
まとめ
Next.jsのServer Actionsは、フォーム処理やデータ更新をシンプルに実装するための便利な仕組みです。
しかし、実装が簡潔になることと、セキュリティが自動的に保証されることは異なります。
特に重要なのは、以下のポイントです。
- Server Actionsの基本的な仕組みを理解する
- Zodなどを利用して入力値を検証する
- サーバー側で認証と認可を実施する
-
useFormStatusで送信状態を表示する - データベース更新時の整合性を考慮する
- キャッシュの更新方法を適切に選択する
Next.jsの機能を活用しながら、セキュリティ、パフォーマンス、ユーザー体験をバランスよく設計することが、保守しやすいWebアプリケーションの開発につながります。