© TOOLS BOX — Next.js / React / TypeScript コードサンプル集

サンプルガイド
←サンプル一覧
nextjsform

Next.js 15 + React Hook Form + Zod でログインフォームを作る

Next.js App Router 環境で react-hook-form と zod を使ったログインフォームの実装例。クライアントバリデーションとエラー表示を型安全に扱う。

難易度: 中級·更新: 2026-04-14·
react-hook-formzodtailwindcss

対応バージョン

nextjs 15react 19typescript 5react-hook-form 7zod 3tailwindcss 4

前提環境

Next.js App Router の基本的な使い方を理解していること

概要

react-hook-form と zod を組み合わせることで、型安全なフォームバリデーションを実現する。 zodResolver を使うことでスキーマ定義とフォームロジックを分離できる。

インストール

npm install react-hook-form @hookform/resolvers zod

スキーマ定義

// src/lib/schemas/login.ts
import { z } from "zod";

export const loginSchema = z.object({
  email: z.string().email("有効なメールアドレスを入力してください"),
  password: z.string().min(8, "パスワードは 8 文字以上で入力してください"),
});

export type LoginInput = z.infer<typeof loginSchema>;

フォームコンポーネント

// src/components/LoginForm.tsx
"use client";

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { loginSchema, type LoginInput } from "@/lib/schemas/login";

export function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<LoginInput>({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = async (data: LoginInput) => {
    // TODO: API 呼び出し
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4 max-w-sm">
      <div>
        <label htmlFor="email" className="block text-sm font-medium">
          メールアドレス
        </label>
        <input
          id="email"
          type="email"
          {...register("email")}
          className="mt-1 block w-full rounded border px-3 py-2 text-sm"
        />
        {errors.email && (
          <p className="mt-1 text-xs text-red-600">{errors.email.message}</p>
        )}
      </div>

      <div>
        <label htmlFor="password" className="block text-sm font-medium">
          パスワード
        </label>
        <input
          id="password"
          type="password"
          {...register("password")}
          className="mt-1 block w-full rounded border px-3 py-2 text-sm"
        />
        {errors.password && (
          <p className="mt-1 text-xs text-red-600">{errors.password.message}</p>
        )}
      </div>

      <button
        type="submit"
        disabled={isSubmitting}
        className="w-full rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
      >
        {isSubmitting ? "ログイン中..." : "ログイン"}
      </button>
    </form>
  );
}

ポイント

  • zodResolver でスキーマとフォームを接続する
  • errors は Zod のエラーメッセージを型安全に参照できる
  • isSubmitting で二重送信を防止する

注意点

Server Actions と組み合わせる場合は server-actions サンプルも参照すること

関連サンプル

同じテーマや技術スタックを使った実装例

  • Next.js 15 Server Actions でフォーム送信を実装する

    Next.js App Router の Server Actions を使ってフォーム送信を実装する例。useActionState と Zod によるサーバー側バリデーションを示す。

  • React Hook Form の useFieldArray で動的フィールドを追加・削除する

    useFieldArray を使い、フォーム内のフィールドを動的に追加・削除・並び替えする実装例。Zod で配列バリデーションも行う。

  • React Hook Form でファイルアップロード対応フォームを実装する

    React Hook Form の register でファイル入力を管理し、Zod でファイルサイズ・拡張子バリデーションを行う実装例。

  • Zod スキーマをフォームバリデーションと API レスポンス検証で共有する

    同一の Zod スキーマを React Hook Form のバリデーションと fetch レスポンス検証の両方で再利用し、型定義の重複をなくす実装例。

  • MUI + React Hook Form で入力フォームを組む

    MUI の TextField / Button を react-hook-form の Controller でラップし、Zod でバリデーションする実装例。MUI の非標準 input と react-hook-form を正しく接続する方法を扱う。

関連仕様

このサンプルを理解するのに役立つ仕様や概念

  • FrameworkNext.jsReact ベースのフルスタックフレームワーク。SSR・SSG・App Router・API Routes を提供する。
  • LibraryZodTypeScript ファーストのスキーマバリデーションライブラリ。型推論と実行時バリデーションを同一スキーマで行える。
←サンプル一覧に戻る