zod vs yup 徹底比較

zod の詳細yup の詳細
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

Zod vs Yup — TypeScriptバリデーションライブラリ徹底比較

1. 結論

TypeScriptを主軸とした開発であればZodを選ぶべきです。 Zodはスキーマ定義から型を自動推論できるため、型定義の二重管理が不要になります。一方、既存のJavaScriptプロジェクトやFormikとの連携が前提であれば、Yupも依然として有力な選択肢です。 新規プロジェクトでは、エコシステムの成長速度と型安全性の観点からZodを第一候補にすることを推奨します。


2. 比較表

項目ZodYup
GitHub Stars(2025年時点)約 36,000+約 23,000+
npm 週間DL数約 2,500万+約 800万+
バンドルサイズ(minified + gzip)約 14 kB約 13 kB
TypeScript対応✅ TypeScript-first(TSで書かれている)⚠️ 型定義あり(後付け対応)
型推論(スキーマ → 型)z.infer<typeof schema>⚠️ InferType<typeof schema>(制限あり)
API設計イミュータブル・関数型寄りメソッドチェーン・OOP寄り
非同期バリデーション.refine() / .superRefine().test()
カスタムエラーメッセージ✅ 柔軟に設定可能✅ 柔軟に設定可能
i18n(国際化)コミュニティライブラリで対応コミュニティライブラリで対応
Formikとの連携アダプター経由で可能✅ 公式サポート
React Hook Formとの連携@hookform/resolvers 公式対応@hookform/resolvers 公式対応
tRPC / Next.js連携✅ ファーストクラスサポート⚠️ 非公式
学習コスト中(独自APIの理解が必要)低(直感的なAPI)
初回リリース2020年2014年

3. それぞれの強み

Zodの強み

① 完全な型推論

Zodの最大の強みは、スキーマ定義から TypeScript の型を完全に推論できる点です。型定義とバリデーションロジックが常に同期するため、「型は通るがバリデーションで落ちる」といった不整合が原理的に発生しません。

② イミュータブルなAPI設計

すべてのメソッドが新しいスキーマインスタンスを返すため、副作用を気にせずスキーマを合成・拡張できます。

③ モダンなエコシステムとの親和性

tRPC、Next.js(Server Actions)、Conform、Hono など、2020年代のモダンなフレームワークがZodをファーストクラスでサポートしています。

④ discriminatedUnion / branded types など高度な型表現

TypeScriptの高度な型パターンに対応しており、複雑なドメインモデルも表現できます。


Yupの強み

① 直感的で読みやすいAPI

メソッドチェーンベースのAPIは、バリデーションルールを英語のように読めるため、チーム内の非TypeScriptエンジニアにも理解しやすいです。

② 長い実績と安定性

2014年から開発が続いており、本番環境での実績が豊富です。枯れたライブラリとしての信頼性があります。

③ Formikとのネイティブ連携

Formikを使ったフォーム開発では、Yupスキーマをそのまま validationSchema に渡せるため、追加設定が不要です。

④ 条件付きバリデーションの書きやすさ

when() メソッドによる条件分岐が直感的で、フォームの動的バリデーションに強みがあります。


4. コード例で比較

ユーザー登録フォームのバリデーション

同じ要件を両ライブラリで実装します。

要件:

  • name: 必須、2〜50文字
  • email: 必須、メール形式
  • age: 任意、18以上120以下の整数
  • role: "admin" | "user" のいずれか
  • password: 必須、8文字以上、英数字混在

Zodの場合

import { z } from "zod";

// スキーマ定義
const userSchema = z.object({
  name: z
    .string()
    .min(2, "名前は2文字以上で入力してください")
    .max(50, "名前は50文字以内で入力してください"),
  email: z
    .string()
    .email("有効なメールアドレスを入力してください"),
  age: z
    .number()
    .int("年齢は整数で入力してください")
    .min(18, "18歳以上である必要があります")
    .max(120, "有効な年齢を入力してください")
    .optional(),
  role: z.enum(["admin", "user"]),
  password: z
    .string()
    .min(8, "パスワードは8文字以上で入力してください")
    .regex(
      /^(?=.*[a-zA-Z])(?=.*\d)/,
      "パスワードは英字と数字の両方を含めてください"
    ),
});

// スキーマから型を自動推論(型定義の二重管理が不要)
type User = z.infer<typeof userSchema>;
// => {
//   name: string;
//   email: string;
//   age?: number | undefined;
//   role: "admin" | "user";
//   password: string;
// }

// バリデーション実行
const result = userSchema.safeParse({
  name: "田中太郎",
  email: "tanaka@example.com",
  age: 30,
  role: "admin",
  password: "securePass1",
});

if (result.success) {
  // result.data は User 型として推論される
  console.log("有効なデータ:", result.data);
} else {
  // エラー情報を構造化して取得できる
  console.error("バリデーションエラー:", result.error.issues);
}

Yupの場合

import * as yup from "yup";

// スキーマ定義
const userSchema = yup.object({
  name: yup
    .string()
    .required("名前は必須です")
    .min(2, "名前は2文字以上で入力してください")
    .max(50, "名前は50文字以内で入力してください"),
  email: yup
    .string()
    .required("メールアドレスは必須です")
    .email("有効なメールアドレスを入力してください"),
  age: yup
    .number()
    .integer("年齢は整数で入力してください")
    .min(18, "18歳以上である必要があります")
    .max(120, "有効な年齢を入力してください")
    .optional(),
  role: yup
    .string()
    .required("ロールは必須です")
    .oneOf(["admin", "user"] as const, "admin または user を選択してください"),
  password: yup
    .string()
    .required("パスワードは必須です")
    .min(8, "パスワードは8文字以上で入力してください")
    .matches(
      /^(?=.*[a-zA-Z])(?=.*\d)/,
      "パスワードは英字と数字の両方を含めてください"
    ),
});

// 型推論(Zodほどの精度ではない)
type User = yup.InferType<typeof userSchema>;
// => role は string 型になる("admin" | "user" のリテラル型にならない場合がある)

// バリデーション実行(非同期)
async function validateUser() {
  try {
    const validData = await userSchema.validate(
      {
        name: "田中太郎",
        email: "tanaka@example.com",
        age: 30,
        role: "admin",
        password: "securePass1",
      },
      { abortEarly: false } // すべてのエラーを収集
    );
    console.log("有効なデータ:", validData);
  } catch (err) {
    if (err instanceof yup.ValidationError) {
      console.error("バリデーションエラー:", err.errors);
    }
  }
}

validateUser();

コード例から見える違い

観点ZodYup
必須/任意の扱いデフォルトが必須(.optional() で任意に).required() を明示的に付ける
バリデーション実行同期(safeParse)/ 非同期(safeParseAsync)を選択可能デフォルトが非同期(validateSync で同期も可能)
エラーハンドリングResult 型パターン(success / errortry-catch パターン
enum の型推論✅ リテラルユニオン型として推論⚠️ string に広がる場合がある

5. どちらを選ぶべきか

✅ Zodを選ぶべきケース

  • 新規のTypeScriptプロジェクト — 型推論の恩恵を最大限に受けられます
  • tRPC / Next.js Server Actions を使う場合 — ファーストクラスサポートがあります
  • APIのリクエスト/レスポンスバリデーション — フロントエンドとバックエンドでスキーマを共有できます
  • 型定義の二重管理を排除したい場合z.infer で型が自動生成されます
  • React Hook Form を使う場合@hookform/resolvers/zod で簡潔に統合できます

✅ Yupを選ぶべきケース

  • 既存のFormikベースのプロジェクト — 移行コストをかけずにそのまま使えます
  • JavaScriptプロジェクト(型推論が不要) — Yupの直感的なAPIが活きます
  • チームにTypeScript経験が浅いメンバーがいる場合 — 学習コストが低いです
  • 既にYupで大量のスキーマが定義されている場合 — 動いているものを無理に移行する必要はありません

⚠️ 移行を検討すべきタイミング

既存のYupプロジェクトでも、以下の状況ではZodへの移行を検討する価値があります。

  • 型定義とバリデーションスキーマの乖離によるバグが頻発している
  • tRPCなどZod前提のライブラリを導入したい
  • TypeScriptの strict モードを有効にして型安全性を高めたい

6. まとめ

ZodとYupはどちらも成熟したバリデーションライブラリですが、設計思想が異なります

ZodYup
設計思想TypeScript-firstJavaScript-first(型は後付け)
向いている時代2020年代のモダンスタック2010年代後半〜のReactエコシステム
一言で表すと「型とバリデーションを統一する」「シンプルにバリデーションする」

2025年現在、TypeScriptの採用率は年々上昇しており、エコシステム全体がZodを中心に動き始めています。新規プロジェクトであれば Zodを標準選択 とし、既存プロジェクトでは 無理に移行せずYupを使い続ける という判断が、最も実務的なアプローチです。

どちらを選んでも「バリデーションをスキーマとして宣言的に書く」という本質は同じです。プロジェクトの状況とチームのスキルセットに合わせて、最適な選択をしてください。