Zod × React Hook Formでリアルタイムバリデーションを実装する
〜Next.js環境での型安全なフォーム体験〜
はじめに
フォームの開発では、「入力中にすぐエラーを見せたい」「入力チェックを型安全に書きたい」といったニーズがあります。
React Hook Form(以下RHF)とZodを組み合わせることで、リアルタイムかつ型安全なバリデーションをシンプルに実装できます。
この記事では、Next.js + TypeScript環境でのZod × RHF連携方法を具体的に紹介します。
セットアップ
まず必要なパッケージをインストールします。
pnpm add react-hook-form @hookform/resolvers zod
スキーマ定義
まずはZodでフォーム入力のスキーマを定義します。
// src/lib/validators/contactSchema.ts
import { z } from "zod";
export const contactSchema = z.object({
name: z.string().min(1, "名前は必須です"),
email: z.string().email("正しいメールアドレスを入力してください"),
message: z.string().min(10, "10文字以上入力してください"),
});
export type ContactInput = z.infer<typeof contactSchema>;
z.infer により、Zodスキーマから型定義を自動生成できます。
この型をRHFにそのまま渡せるのがポイントです。
React Hook Formで利用する
RHFのuseFormを使い、Zodスキーマをバリデーションルールとして適用します。
// src/app/contact/page.tsx
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { contactSchema, ContactInput } from "@/lib/validators/contactSchema";
export default function ContactPage() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<ContactInput>({
resolver: zodResolver(contactSchema),
mode: "onChange", // 入力変更時にリアルタイム検証
});
const onSubmit = (data: ContactInput) => {
console.log("送信データ:", data);
alert("送信完了!");
};
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="max-w-md mx-auto flex flex-col gap-4 mt-8"
>
<div>
<label className="block text-sm font-medium">名前</label>
<input {...register("name")} className="border p-2 w-full" />
{errors.name && <p className="text-red-500">{errors.name.message}</p>}
</div>
<div>
<label className="block text-sm font-medium">メール</label>
<input {...register("email")} className="border p-2 w-full" />
{errors.email && <p className="text-red-500">{errors.email.message}</p>}
</div>
<div>
<label className="block text-sm font-medium">メッセージ</label>
<textarea {...register("message")} className="border p-2 w-full h-24" />
{errors.message && <p className="text-red-500">{errors.message.message}</p>}
</div>
<button
type="submit"
disabled={isSubmitting}
className="bg-blue-600 text-white py-2 rounded disabled:opacity-50"
>
送信
</button>
</form>
);
}
zodResolver を設定するだけで、ZodのスキーマがRHFに統合され、リアルタイムバリデーションが自動的に実行されます。
よくあるミス:use client の付け忘れ
RHFはクライアント側で動作するため、フォームコンポーネントの冒頭に use client を付ける必要があります。
これを忘れると useForm フックがエラーになります。
UX向上のためのヒント
mode: "onChange"または"onBlur"を活用する
入力時点でバリデーションを走らせることで、送信前にエラーを即時表示できます。エラーをまとめて表示する
formState.errorsを活用して、複数項目のエラーを一覧で表示することも可能です。型の再利用
ContactInput型をServer ActionsやAPIハンドラでも再利用することで、フロントとバックの整合性を保てます。
まとめ
Zod × React Hook Formを組み合わせることで、
- フロントエンドのバリデーションロジックを型安全に保ち、
- 入力中に即時フィードバックを返し、
- コード量を最小限に抑えたフォーム体験
を実現できます。
次回は、このフォームをServer Actionsと統合し、クライアントからサーバーまで型安全にデータを流す構成を紹介します。