Conform + Server Actions の紹介と導入【Next.js】
はじめに
先日、React の勉強会で、フォーム実装について取り上げました 📝
フォーム処理とバリデーションは、Web アプリケーション開発において重要でありながら、意外と複雑になりがちな要素です。
今回は、Conform と Server Actions について調査したので、基礎的な内容をまとめました!
時間の節約になれば、嬉しいです 🙌
Conform とは?
Conform は、Web の基本原則を活用した型安全なフォームバリデーションライブラリです。
Conform の特徴は、HTML Forms を「プログレッシブエンハンスメント」の手法で強化し、
Remix や Next.js などのサーバーフレームワークと完全に連携できる点にあります。
主な特徴は以下の通りです:
- 型安全:TypeScript との優れた連携
- プログレッシブエンハンスメント:JavaScript が無効でも機能するフォーム設計
- サーバーフレームワークとの連携:Next.js や Remix などと簡単に統合
- DOM ベースの状態管理:FormData Web API を使用して DOM から直接フォームの値を取得
- 柔軟な HTML 構造:フォームのマークアップに制限を設けない
Actions とは?
Actions(以前は Server Actions と呼ばれていました)は Next.js に組み込まれた、サーバーサイドでの処理を行うための機能です。
フォームの送信やデータの更新(ミューテーション)を効率的に処理するための仕組みで、React 19 で安定版となった「Actions」機能を利用しています。
Actions の主な特徴は以下の通りです:
- サーバーサイドの処理:クライアントからサーバーへ直接アクションを呼び出し可能
- API エンドポイント不要:明示的な API ルートを作成せずにデータ操作が可能
- プログレッシブエンハンスメント:JavaScript が読み込まれていない状態でもフォームが機能
Conform の基本的な使い方
Conform は単純明快な API を提供しています。
基本的な使い方を、確認してみます!!
基本的なコンセプト
Conform の中心となるのは useForm
フックです。
このフックは、以下の主要な機能を提供します:
- フォーム状態の管理
- バリデーション処理
- フィールドの自動生成
- エラーハンドリング
以下は、基本的な Conform の使用例です:
import { useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { z } from "zod";
// バリデーションスキーマの定義
const schema = z.object({
username: z.string().min(2, "ユーザー名は2文字以上で入力してください"),
email: z.string().email("有効なメールアドレスを入力してください"),
});
function LoginForm() {
// useForm フックの使用
const [form, fields] = useForm({
// バリデーション処理の定義
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
});
return (
<form id={form.id} onSubmit={form.onSubmit}>
<div>{form.errors}</div>
<div>
<label>ユーザー名</label>
<input name={fields.username.name} />
<div>{fields.username.errors}</div>
</div>
<div>
<label>メールアドレス</label>
<input type="email" name={fields.email.name} />
<div>{fields.email.errors}</div>
</div>
<button>ログイン</button>
</form>
);
}
この例では、Conform の基本的な機能を使用しています:
-
useForm
フックでフォームを初期化 -
parseWithZod
を使って Zod スキーマと連携 -
fields
オブジェクトを使って各フィールドの名前やエラーにアクセス -
form.onSubmit
でフォーム送信処理をハンドリング
Conform の導入手順(Next.js)
それでは、Conform と Server Actions を組み合わせたフォーム実装の手順を見ていきましょう。
1. ライブラリのインストール
npm install @conform-to/react @conform-to/zod zod
これにより、以下のライブラリがインストールされます:
-
@conform-to/react
: React 用の Conform コアライブラリ -
@conform-to/zod
: Conform と Zod を連携させるアダプター -
zod
: バリデーションスキーマライブラリ
2. バリデーションスキーマの定義
まず、Zod を使ってバリデーションルールを定義します:
// schema.ts
import { z } from "zod";
export const contactSchema = z.object({
name: z
.string()
.min(2, { message: "お名前は2文字以上で入力してください" })
.max(50, { message: "お名前は50文字以内で入力してください" }),
email: z
.string()
.email({ message: "有効なメールアドレスを入力してください" }),
message: z
.string()
.min(10, { message: "お問い合わせ内容は10文字以上で入力してください" })
.max(1000, { message: "お問い合わせ内容は1000文字以内で入力してください" }),
});
export type ContactFormValues = z.infer<typeof contactSchema>;
3. Action の作成
次に、フォーム送信を処理する Action を作成します:
// actions.ts
"use server";
import { parseWithZod } from "@conform-to/zod";
import { contactSchema } from "./schema";
import { revalidatePath } from "next/cache";
export async function submitContact(prevState: unknown, formData: FormData) {
// フォームデータのバリデーション
const submission = parseWithZod(formData, {
schema: contactSchema,
});
// バリデーションエラーがある場合は早期リターン
if (submission.status !== "success") {
return submission.reply();
}
// バリデーション成功時のデータ
const validData = submission.value;
try {
// ここでデータベースへの保存やメール送信などの処理を行う
// 例: await saveToDatabase(validData);
// キャッシュの再検証
revalidatePath("/");
// 成功時の処理
return submission.reply({
resetForm: true,
success: "お問い合わせを受け付けました。ありがとうございます!",
});
} catch (error) {
// エラー時の処理
return submission.reply({
error: "エラーが発生しました。再度お試しください。",
});
}
}
この Action では、以下の処理を行っています:
-
parseWithZod
を使ってフォームデータをバリデーション - バリデーション成功時にデータ処理(実際のアプリでは DB への保存など)
-
revalidatePath
を使ってページキャッシュを再検証 - 結果に応じたレスポンスを返却
4. フォームコンポーネントの実装
最後に、Conform と Action を使ったフォームコンポーネントを作成します:
// ContactForm.tsx
"use client";
import { useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { useActionState } from "react-dom";
import { contactSchema } from "./schema";
import { submitContact } from "./actions";
export function ContactForm() {
// useActionState を使って Action を接続
const [lastResult, action] = useActionState(submitContact, undefined);
// useForm を使ってフォーム状態を管理
const [form, fields] = useForm({
// 最後の送信結果を同期
lastResult,
// クライアント側でもバリデーションを実行
onValidate({ formData }) {
return parseWithZod(formData, { schema: contactSchema });
},
// バリデーションのタイミング設定
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
});
return (
<form id={form.id} onSubmit={form.onSubmit} action={action} noValidate>
{/* フォーム全体のエラー/成功メッセージ */}
{lastResult?.success && (
<div className="success">{lastResult.success}</div>
)}
{lastResult?.error && <div className="error">{lastResult.error}</div>}
<div>
<label htmlFor={fields.name.id}>お名前</label>
<input
id={fields.name.id}
name={fields.name.name}
defaultValue={fields.name.initialValue}
/>
<div className="error">{fields.name.errors}</div>
</div>
<div>
<label htmlFor={fields.email.id}>メールアドレス</label>
<input
id={fields.email.id}
name={fields.email.name}
type="email"
defaultValue={fields.email.initialValue}
/>
<div className="error">{fields.email.errors}</div>
</div>
<div>
<label htmlFor={fields.message.id}>お問い合わせ内容</label>
<textarea
id={fields.message.id}
name={fields.message.name}
defaultValue={fields.message.initialValue}
rows={5}
/>
<div className="error">{fields.message.errors}</div>
</div>
<button type="submit">送信する</button>
</form>
);
}
このコンポーネントでは、以下の機能を実装しています:
-
useActionState
フックで Action との連携 -
useForm
フックでフォーム状態の管理 - クライアント側でのバリデーション設定(
onBlur
とonInput
時) - Server Action からの成功/エラーメッセージの表示
- 各フィールド用の入力とエラー表示
これで、Conform と Server Actions を使った完全なフォーム実装が完成しました 👍
Conform が実現するプログレッシブエンハンスメントとは?
Conform の、重要な特徴の一つに「プログレッシブエンハンスメント」があります。
これは何を意味するのでしょうか?
プログレッシブエンハンスメントとは、基本的な機能をすべてのブラウザで利用可能にしつつ、高度なブラウザではより優れた機能を提供する設計手法です。
Conform のコンテキストでは、これは以下を意味します:
- JavaScript なしでも動作:JS が無効でもフォームは基本的な HTML フォームとして機能
- 拡張された体験:JS が有効な場合は、リアルタイムバリデーションやスムーズな UX を提供
- アクセシビリティ:基本的なフォーム機能がすべてのユーザーに提供される
Actions と組み合わせることで、この原則が完全に実装されます:
- JS がない場合:フォームは通常の HTML フォーム送信として機能し、サーバーサイドでバリデーション
- JS がある場合:クライアントサイドでのバリデーションとスムーズなフォーム体験を提供
これは、React Hook Form などのライブラリとは、異なるアプローチですね!
おわりに
最後まで読んでいただき、ありがとうございます 🥳
ハンズオン形式で、
実際に手を動かしてフォーム実装を学習したい場合は、以下の教材もチェックしてみてください!!
この記事が、少しでも参考になれば嬉しいです!
そして、もし、間違いや補足情報などがありましたら、ぜひコメントを追加してください!
Happy Hacking :)
参考
Discussion