
Next.jsのエコシステムにおいて、フォームハンドリングは大きなパラダイムシフトを遂げました。Next.js 14の安定版リリースにより、冗長なクライアントサイドのフォームハンドラーを記述したり、過剰なローディング状態を管理したり、従来のAPIルートと格闘したりする時代は終わりました。Server Actionsは、Reactのモダンなフック(useFormStateとuseFormStatus)と組み合わせることで、堅牢なフォームを構築するための深く統合された、段階的に強化されたアプローチを提供します。
Zodのようなスキーマ検証ライブラリと組み合わせることで、開発者はクライアントとサーバーの境界を越えて厳格な型安全性を強制できるようになりました。この包括的なガイドでは、Next.js 14でフォームを処理する方法を探り、従来のクライアントコンポーネントとモダンなServer Actionsのアプローチを比較します。
Next.js 14とApp Routerの成熟以前は、フォームハンドリングは主にクライアントサイドで行われていました。開発者はReactの状態(useState)や、React Hook FormやFormikのような専用ライブラリに大きく依存していました。典型的なフローは次のとおりです。
isLoading、isError、およびisSuccessの状態を手動で管理する。このアプローチは機能しますが、大量のボイラープレートコードが発生し、より多くのJavaScriptをクライアントにプッシュすることになります。典型的なクライアントサイドのフォームは次のようになります。
'use client';
import { useState } from 'react';
export default function TraditionalForm() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData);
try {
const res = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Submission failed');
// Handle success
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="title" required />
<button type="submit" disabled={loading}>
{loading ? 'Submitting...' : 'Submit'}
</button>
{error && <p>{error}</p>}
</form>
);
}
このパターンは効果的ですが、冗長です。完全に独立したAPIルートが必要であり、コンポーネントに'use client'をマークすることを強制するため、クライアントバンドルサイズが増加します。
Server Actionsは、非同期のサーバー関数をReactコンポーネント内(または別のファイル内)に直接定義し、それを<form>要素のaction属性から直接呼び出すことを可能にすることで、このパラダイムを覆します。
Server Actionsの核心は、サーバー上で実行される非同期関数です。これらはNext.jsのキャッシュおよび再検証システムとシームレスに統合されており、手動で状態の無効化ロジックを記述することなく、データを変更してUIを即座に更新できます。
Server Actionsを使用して、前の例をリファクタリングする方法を見てみましょう。
import { revalidatePath } from 'next/cache';
// This function runs exclusively on the server
async function submitData(formData) {
'use server';
const title = formData.get('title');
const content = formData.get('content');
// Perform database mutation here
// await db.posts.create({ title, content });
// Revalidate the cache to show the new post
revalidatePath('/posts');
}
export default function ServerActionForm() {
return (
<form action={submitData}>
<input type="text" name="title" required />
<textarea name="content" required />
<button type="submit">Submit</button>
</form>
);
}
'use client'、useState、およびfetchがないことに注目してください。このフォームは、ユーザーがブラウザでJavaScriptを無効にしている場合でも機能し、すぐに使えるプログレッシブエンハンスメントを提供します。しかし、ローディング状態や検証エラーについてはどうでしょうか?そこでReactの新しいフックとZodが活躍します。
useFormStateとuseFormStatusによる状態管理リッチなユーザーエクスペリエンスを作成するには、フォーム送信中にフィードバックを提供する必要があります。Reactは、Server Actionsを使用するフォームのために特別に設計された2つの新しいフックを導入しました。
useFormStatususeFormStatusは、最後のフォーム送信のステータス情報を提供します。これは、<form>の内部でレンダリングされるコンポーネントで使用する必要があります。
'use client';
import { useFormStatus } from 'react-dom';
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending} className="bg-blue-600 text-white px-4 py-2 rounded">
{pending ? 'Submitting...' : 'Save Post'}
</button>
);
}
useFormStateuseFormStateを使用すると、Server Actionの結果に基づいて状態を更新できます。これは、検証エラーや成功メッセージを処理するのに最適です。useFormStateとZodを組み合わせて、堅牢なスキーマ駆動型検証を行いましょう。
Zodは、TypeScriptファーストのスキーマ宣言および検証ライブラリです。Server Actionsと組み合わせると、データベースに到達するデータが期待どおりであることを保証する上で非常に強力です。
まず、スキーマとフォームの状態の形状を定義します。
import { z } from 'zod';
const postSchema = z.object({
title: z.string().min(3, 'Title must be at least 3 characters'),
content: z.string().min(10, 'Content must be at least 10 characters'),
});
次に、Server Actionを作成します。これはZodのsafeParseを使用して、受信するFormDataを検証します。
'use server';
import { revalidatePath } from 'next/cache';
// Assume postSchema is imported here
export async function createPost(prevState, formData) {
// Validate form fields using Zod
const validatedFields = postSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
});
// If form validation fails, return errors early
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to Create Post.',
};
}
// Mutate data
try {
// await db.posts.create(validatedFields.data);
revalidatePath('/posts');
return { message: 'Post created successfully!', errors: {} };
} catch (error) {
return { message: 'Database Error: Failed to Create Post.', errors: {} };
}
}
最後に、これらすべてをクライアントコンポーネントで結合します。フォームのレンダリングと状態管理はクライアントで行われますが、実際の実行と検証はサーバー上で安全に維持されます。
'use client';
import { useFormState } from 'react-dom';
import { createPost } from '@/app/actions';
import { SubmitButton } from './SubmitButton';
export default function CreatePostForm() {
const initialState = { message: null, errors: {} };
const [state, dispatch] = useFormState(createPost, initialState);
return (
<form action={dispatch} className="flex flex-col gap-4 max-w-md">
<div>
<label htmlFor="title">Title</label>
<input id="title" name="title" type="text" className="border p-2 w-full" />
{state.errors?.title && (
<p className="text-red-500 text-sm mt-1">{state.errors.title[0]}</p>
)}
</div>
<div>
<label htmlFor="content">Content</label>
<textarea id="content" name="content" className="border p-2 w-full" />
{state.errors?.content && (
<p className="text-red-500 text-sm mt-1">{state.errors.content[0]}</p>
)}
</div>
<SubmitButton />
{state.message && (
<p className="text-sm text-gray-700 bg-gray-100 p-3 rounded" aria-live="polite">
{state.message}
</p>
)}
</form>
);
}
Server ActionsはNext.jsのデータ変更の未来を象徴していますが、従来のクライアントコンポーネントも複雑なアプリケーションで依然としてその役割を果たします。
revalidatePathまたはrevalidateTagを使用)。あるチームが、従来のクライアントサイドReactコンポーネントからNext.js 14 Server Actionsへ複雑なフォームを移行しています。このフォームには、複数の入力フィールド、即時フィードバックのためのクライアントサイドバリデーション(例:パスワードの強度)、およびデータベースと連携する最終的な送信が含まれます。優れたユーザーエクスペリエンスを維持しながらServer Actionsを最大限に活用するための最も効果的な戦略は何ですか?
Next.js 14は、フォームハンドリングに関する開発者のエクスペリエンスを劇的に簡素化しました。Server Actions、useFormState、useFormStatus、およびZodを採用することで、非常に堅牢で高性能、かつ型安全なフォームを構築できます。このアーキテクチャは、任意のローディング状態やエラー状態を手動で管理する認知的負荷を軽減するだけでなく、クライアントサイドのレンダリングとサーバーサイドの実行の間でよりクリーンな関心の分離を強制します。
Reactエコシステムが進化し続ける中で、これらのネイティブAPIを標準化することは、アプリケーションが堅牢でアクセスしやすく、ウェブの未来に対応できることを保証します。過去の重厚なアプローチから、これらのプログレッシブエンハンスメント対応の方法へと移行することは、現代のウェブ開発者にとって重要な一歩です。
AI CLIログの整形、Cron式の作成、JWTの解析、chmod権限の計算をオフラインで実行。

Next.js15のキャッシュコンポーネントとServer Actionsを網羅した、本番環境レベルのアーキテクチャとコード例を含む完全ガイドです。
続きを読む
Next.js 15とtRPC v11を組み合わせた、エンドツーエンドの型安全性、Server Actions、TanStack Query v5を網羅する、本番環境レベルのアーキテクチャとコード例を含む包括的なガイドです。
続きを読む
TanStack Query v5とNext.js 15を組み合わせたOptimistic Updates、Cache Sync、Server Actionsの実装を、本番環境レベルのアーキテクチャとコード例で解説する包括的なガイドです。
続きを読む