Zod vs Valibot — TypeScriptバリデーションライブラリ徹底比較
1. 結論
バンドルサイズを極限まで削りたいなら Valibot、エコシステムの充実度と安定性を重視するなら Zod を選んでください。 Valibot はツリーシェイキングに最適化された関数ベース設計で、使った分だけバンドルに含まれます。一方 Zod は圧倒的なユーザー数とサードパーティ連携の豊富さで、プロダクション導入のハードルが低いライブラリです。
2. 比較表
| 観点 | Zod (v3.x) | Valibot (v1.x) |
|---|---|---|
| GitHub Stars | ≈ 35,000+ | ≈ 7,000+ |
| npm 週間DL数 | ≈ 2,500万+ | ≈ 200万+ |
| ミニファイ+gzip サイズ | 約 14 kB(全体) | 約 1〜5 kB(使用分のみ) |
| ツリーシェイキング | △(メソッドチェーンのため限定的) | ◎(関数ベースで完全対応) |
| API スタイル | メソッドチェーン(OOP 風) | パイプ+関数合成(FP 風) |
| TypeScript 対応 | ◎(TypeScript-first) | ◎(TypeScript-first) |
型推論 (Infer) | ◎ | ◎ |
| エラーメッセージのカスタマイズ | ◎(errorMap / メッセージ引数) | ◎(各バリデーションに直接指定) |
| 非同期バリデーション | ◎ | ◎ |
| エコシステム連携 | ◎(React Hook Form, tRPC, Conform, Drizzle 等多数) | ○(対応拡大中、主要ライブラリは対応済み) |
| 学習コスト | 低〜中 | 低〜中 |
| 初回リリース | 2020年 | 2023年 |
| ランタイム依存 | なし | なし |
3. それぞれの強み
Zod の強み
- 圧倒的なエコシステム: tRPC・React Hook Form・Conform・Next.js Server Actions・Drizzle ORM など、多くのライブラリが Zod をファーストクラスでサポートしています。「とりあえず Zod を入れておけば連携で困らない」という安心感があります。
- メソッドチェーンの直感性:
z.string().email().min(1)のようにドットで繋ぐ API は、IDE の補完との相性が良く、スキーマの可読性も高いです。 - 成熟度と情報量: Stack Overflow・Zenn・Qiita などの日本語記事も豊富で、トラブルシューティングが容易です。
.transform()/.refine()の柔軟性: バリデーションとデータ変換をスキーマ内で完結でき、複雑なビジネスロジックにも対応しやすい設計です。
Valibot の強み
- バンドルサイズの圧倒的な小ささ: 関数単位でインポートするため、使わないバリデーションはバンドルに含まれません。Zod 比で最大 90% 以上 のサイズ削減が可能です。
- 関数ベースの合成可能な設計:
pipe()で関数を合成する FP スタイルにより、カスタムバリデーションの作成・再利用が自然に行えます。 - モジュラーアーキテクチャ: 各機能が独立した関数として提供されるため、バンドラーの最適化が最大限に効きます。
- Zod 互換のマイグレーションパス: API の概念(
parse・safeParse・Inferなど)が Zod と似ているため、Zod 経験者は短時間で移行できます。
4. コード例で比較
4-1. 基本的なユーザースキーマの定義と型推論
Zod
import { z } from "zod";
const UserSchema = z.object({
name: z.string().min(1, "名前は必須です"),
email: z.string().email("有効なメールアドレスを入力してください"),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(["admin", "editor", "viewer"]),
});
// 型を自動推論
type User = z.infer<typeof UserSchema>;
// => { name: string; email: string; age?: number; role: "admin" | "editor" | "viewer" }
// バリデーション実行
const result = UserSchema.safeParse({
name: "田中太郎",
email: "tanaka@example.com",
role: "admin",
});
if (result.success) {
console.log(result.data); // 型安全な User オブジェクト
} else {
console.error(result.error.issues);
}
Valibot
import * as v from "valibot";
const UserSchema = v.object({
name: v.pipe(v.string(), v.minLength(1, "名前は必須です")),
email: v.pipe(v.string(), v.email("有効なメールアドレスを入力してください")),
age: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150))),
role: v.picklist(["admin", "editor", "viewer"]),
});
// 型を自動推論
type User = v.InferOutput<typeof UserSchema>;
// => { name: string; email: string; age?: number; role: "admin" | "editor" | "viewer" }
// バリデーション実行
const result = v.safeParse(UserSchema, {
name: "田中太郎",
email: "tanaka@example.com",
role: "admin",
});
if (result.success) {
console.log(result.output); // 型安全な User オブジェクト
} else {
console.error(result.issues);
}
4-2. カスタムバリデーション(パスワード確認の一致チェック)
Zod
import { z } from "zod";
const PasswordFormSchema = z
.object({
password: z.string().min(8, "8文字以上で入力してください"),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: "パスワードが一致しません",
path: ["confirmPassword"],
});
type PasswordForm = z.infer<typeof PasswordFormSchema>;
const result = PasswordFormSchema.safeParse({
password: "securePass123",
confirmPassword: "securePass999",
});
if (!result.success) {
// [{ message: "パスワードが一致しません", path: ["confirmPassword"], ... }]
console.error(result.error.issues);
}
Valibot
import * as v from "valibot";
const PasswordFormSchema = v.pipe(
v.object({
password: v.pipe(v.string(), v.minLength(8, "8文字以上で入力してください")),
confirmPassword: v.string(),
}),
v.forward(
v.check(
(data) => data.password === data.confirmPassword,
"パスワードが一致しません"
),
["confirmPassword"]
)
);
type PasswordForm = v.InferOutput<typeof PasswordFormSchema>;
const result = v.safeParse(PasswordFormSchema, {
password: "securePass123",
confirmPassword: "securePass999",
});
if (!result.success) {
// [{ message: "パスワードが一致しません", path: [...], ... }]
console.error(result.issues);
}
4-3. データ変換(transform)
Zod
import { z } from "zod";
const DateStringSchema = z
.string()
.datetime()
.transform((val) => new Date(val));
type Result = z.infer<typeof DateStringSchema>;
// => Date
const date = DateStringSchema.parse("2025-01-15T09:00:00.000Z");
console.log(date instanceof Date); // true
Valibot
import * as v from "valibot";
const DateStringSchema = v.pipe(
v.string(),
v.isoTimestamp(),
v.transform((val) => new Date(val))
);
type Result = v.InferOutput<typeof DateStringSchema>;
// => Date
const date = v.parse(DateStringSchema, "2025-01-15T09:00:00.000Z");
console.log(date instanceof Date); // true
5. どちらを選ぶべきか — ユースケース別の推奨
Zod を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| tRPC / React Hook Form との連携が前提 | ファーストクラスサポートがあり、設定がほぼ不要 |
| チーム開発で統一ライブラリを決めたい | 情報量・採用実績が圧倒的に多く、オンボーディングが楽 |
| サーバーサイド(API バリデーション)中心 | バンドルサイズの制約が緩く、エコシステムの恩恵を最大限に受けられる |
| 既存プロジェクトで Zod が使われている | 無理に移行するメリットは薄い |
Valibot を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| クライアントサイドのバンドルサイズを極限まで削りたい | ツリーシェイキングにより使用分だけバンドルされる |
| Edge Runtime / Cloudflare Workers | サイズ制限が厳しい環境で真価を発揮 |
| 関数型プログラミングスタイルを好むチーム | pipe() ベースの合成が自然にフィットする |
| 新規プロジェクトで軽量スタートしたい | 依存の少なさと小さなフットプリントが魅力 |
| Zod からの段階的移行を検討中 | 概念が似ているため学習コストが低い |
どちらでも良いケース
- 小〜中規模の個人プロジェクト: どちらを選んでも開発体験に大きな差はありません。好みの API スタイルで選んで問題ありません。
6. まとめ
Zod と Valibot は、どちらも TypeScript ファーストのバリデーションライブラリとして非常に高い完成度を持っています。解決する課題は同じですが、設計思想が異なります。
- Zod は「メソッドチェーンによる直感的な API」と「巨大なエコシステム」が武器です。迷ったらまず Zod を選べば、情報量とサードパーティ連携で困ることはほぼありません。
- Valibot は「関数ベースのモジュラー設計」と「圧倒的なバンドルサイズの小ささ」が武器です。クライアントサイドのパフォーマンスやエッジ環境での制約がある場合に、強力な選択肢となります。
最終的には、プロジェクトの制約(バンドルサイズ・実行環境・チームの慣れ・連携ライブラリ) に応じて選択するのがベストです。どちらも活発にメンテナンスされており、長期的に安心して採用できるライブラリです。