🎉

Zodに対抗するバリデーションライブラリ(Valibot・ArkType)を比較検証

に公開

はじめまして、_minoです!

この記事では、フロントエンド開発におけるバリデーションで Zod が最適なのか、ほかにより適したバリデーションライブラリがあるのではないかと思い、いくつかのトレンドのライブラリを候補に挙げて比較・検証した結果をまとめました。
バリデーションライブラリ選びで迷っている方は、参考にしていただければ幸いです。

「バリデーションって何?」という方には、こちらの解説がわかりやすいです!
https://wa3.i-3-i.info/word11610.html

🚀 対象ライブラリ

Zod / v4

TypeScript-first な最も人気のスキーマバリデーションライブラリ。スキーマから型を自動生成し、データの安全性を保証する。

機能・特徴:

  • チェーンメソッドで直感的な記法(z.string().email().min(5)
  • .safeParse() でtry-catch不要のエラーハンドリング
  • 豊富なスキーマ操作(拡張、選択、変換など)
  • 型変換機能(文字列→数値の自動変換など)
  • 広範なエコシステム

https://zod.dev/

Valibot / v1

モジュラー設計により90%以上のバンドルサイズ削減を実現する次世代バリデーションライブラリ。Zodの軽量代替として開発された。

機能・特徴:

  • パイプライン記法(v.pipe(v.string(), v.email())で文字列→メール形式の順番に検証)
  • 使った機能のみバンドルされる軽量設計
  • 小さな関数を組み合わせるモジュラー構造
  • 高速なバリデーション処理とコンパイル
  • モバイル・Edge環境での最適化

https://valibot.dev/

ArkType / v2

TypeScript記法に最も近い文字列ベースの革新的なバリデーションライブラリ。内蔵型システムによる自動最適化が特徴。

機能・特徴:

  • TypeScript風の短縮記法('string>=2'で「2文字以上の文字列」)
  • ユニオン型の判定を内部的に最適化
  • 型の矛盾(数値かつ文字列など)を事前検出
  • エディタ上でリアルタイムにエラーを表示
  • ランタイムバリデーションが高速(大量・高頻度処理に最適)

https://arktype.io/

📚トレンド・人気

Star Historyチャート(GitHubスター数の推移)はこちらになります。

Zodの勢いは依然として衰えることなく、他のバリデーションライブラリと比較しても圧倒的です。
v4のアップグレードによる開発体験の大幅向上により、今後さらに人気が加速していくと思います。

一方で、ValibotArkTypeも着実に人気を集めており、注目すべき存在となっています。

両ライブラリとも継続的なアップデートが行われ、エコシステムも徐々に活発化しているため、将来的にはZodの対抗になる可能性もありそうです。

📊 比較検証・ベンチマーク

以下の測定条件でそれぞれのライブラリの比較検証を実施しました。

測定環境

  • Next.js 15.5.4
  • Node.js v20.10.0
  • TypeScript 5.x
  • 測定ツール: performance.now()

測定単位

  • μs(マイクロ秒): 100万分の1秒(0.000001秒)
  • ops/s: 1秒間に実行可能な処理回数
  • ms(ミリ秒): 1000分の1秒(0.001秒)

実行速度比較

シンプルなスキーマ - 正常データ

ライブラリ 平均実行時間 処理速度 相対性能
Arktype 0.775 μs 1,281,600 ops/s 1.00倍(基準)
Zod 0.927 μs 1,066,714 ops/s 1.20倍遅い
Valibot 0.943 μs 1,030,532 ops/s 1.24倍遅い

※ すべてのバリデーションルールを満たす正しいデータでの測定

シンプルなスキーマ - エラーデータ

ライブラリ 平均実行時間 処理速度 相対性能
Arktype 0.518 μs 1,930,050 ops/s 1.00倍(基準)
Valibot 3.599 μs 278,022 ops/s 6.94倍遅い
Zod 20.620 μs 48,931 ops/s 39.46倍遅い

※ バリデーションエラーが発生する不正なデータでの測定(例:文字数不足、メール形式違反)

複雑なスキーマ

ライブラリ 平均実行時間 処理速度 相対性能
Arktype 0.003 ms 378,972 ops/s 1.00倍(基準)
Zod 0.059 ms 17,271 ops/s 約21.94倍遅い
Valibot 0.105 ms 9,593 ops/s 約39.50倍遅い

※ ネストされたオブジェクトと配列を含む大規模データでの測定

バンドルサイズ比較

プロダクションビルドサイズ(Next.js実測値)

ライブラリ ページサイズ First Load JS node_modules Tree-shaking効率
Valibot 1.35 KB 127 KB 1.7 MB
Zod 1.30 KB 171 KB 5.6 MB
Arktype 1.58 KB 162 KB 628 KB ⚪︎

補足

  • Valibotのモジュラー設計により最高のTree-shaking効率を実現
  • Arktypeはnode_modulesサイズは最小だが、JITコンパイラを含むためビルド後は中程度
  • Zodは機能豊富な分、最大のバンドルサイズ

初期化パフォーマンス

ライブラリ スキーマ作成時間 初回実行 2回目以降 JITコンパイル
Valibot 0.014 ms 0.119 ms 0.012 ms なし
Arktype 0.121 ms 0.666 ms 0.010 ms あり(66x高速化)
Zod 0.243 ms 0.516 ms 0.014 ms あり(36x高速化)

補足

  • Arktypeは初回から高速(JIT最適化済み)
  • Zodはv4でJIT導入により大幅改善
  • Valibotは初期化重視の設計でTTI(Time to Interactive)が優秀

その他の比較項目

項目 Zod Valibot ArkType
実行速度(正常データ) ⚪︎ 普通 ⚪︎ 普通 ◎ 高速
実行速度(エラーデータ) △ 低速 ⚪︎ 普通 ◎ 高速
実行速度(複雑なスキーマ) ⚪︎ 普通 △ 低速 ◎ 高速
バンドルサイズ △ 大 ◎ 最小 ⚪︎ 中
Next.js統合 ◎ 優秀 ⚪︎ 良好 ⚪︎ 良好
エラーメッセージ ◎ 詳細 ⚪︎ 良好 ⚪︎ 良好
カスタムバリデーション ◎ 柔軟 ◎ 柔軟 ⚪︎ 制限あり
非同期バリデーション ◎ 対応 ◎ 対応 △ 未対応(外部実装必要)
サーバーサイド利用 ◎ 完全対応(Node.js最適化) ◎ 完全対応(軽量設計) ⚪︎ 対応(一部制限)
型推論性能 ⚪︎ 普通(複雑時重い) ◎ 高速(効率的) ◎ 最高速(文字列解析)
Edge Runtime ◎ 対応(安定動作) ◎ 対応(最適化済み) ⚪︎ 部分対応(要検証)

🧑‍💻 実装方法の比較

「企業向けのお問い合わせフォーム」を例に、それぞれのバリデーションライブラリを使った実装方法を比較します。

共通のデータ構造として以下の5つが入力データとして必須の項目となります。

// 各ライブラリで共通して使用するお問い合わせフォームの構造
interface ContactForm {
  company: string;      // 会社名(必須)
  name: string;         // 名前(必須)  
  email: string;        // メールアドレス(必須、email形式)
  category: 'design' | 'engineering' | 'sales' | 'marketing';  // カテゴリ(必須、選択式)
  message: string;      // メッセージ(必須)
}

Zod

スキーマ定義

import { z } from "zod"

// メソッドチェーン方式でバリデーションルールを定義
const contactSchema = z.object({
    company: z.string()
        .min(1, "会社名を入力してください")
        .max(100, "会社名は100文字以内で入力してください"),
    name: z.string()
        .min(1, "名前を入力してください")
        .max(50, "名前は50文字以内で入力してください"),
    email: z.string()
        .min(1, "メールアドレスを入力してください")
        .email("正しいメールアドレスを入力してください"),
    category: z.enum(["design", "engineering", "sales", "marketing"], {
        message: "カテゴリを選択してください",
    }),
    message: z.string()
        .min(1, "お問い合わせ内容を入力してください")
        .max(1000, "お問い合わせ内容は1000文字以内で入力してください"),
})

// 型推論
type ContactForm = z.infer<typeof contactSchema>

バリデーション実行

const handleSubmit = async (data: ContactForm) => {
    const result = contactSchema.safeParse(data)

    if (result.success) {
        // 成功時: result.dataに検証済みデータ
        console.log("Validated Data:", result.data)
    } else {
        // 失敗時: result.error.issuesにエラー情報
        const fieldErrors: Record<string, string> = {}
        result.error.issues.forEach((issue) => {
            const fieldName = issue.path[0] as string
            if (fieldName && !fieldErrors[fieldName]) {
                fieldErrors[fieldName] = issue.message
            }
        })
        // エラー表示処理
    }
}

Valibot

スキーマ定義

import * as v from "valibot"

// パイプライン方式でバリデーションを組み合わせ
const contactSchema = v.object({
    company: v.pipe(
        v.string(),
        v.minLength(1, "会社名は1文字以上で入力してください"),
        v.maxLength(100, "会社名は100文字以内で入力してください")
    ),
    name: v.pipe(
        v.string(),
        v.minLength(1, "名前は1文字以上で入力してください"),
        v.maxLength(50, "名前は50文字以内で入力してください")
    ),
    email: v.pipe(
        v.string(),
        v.minLength(1, "メールアドレスは1文字以上で入力してください"),
        v.email("正しいメールアドレスを入力してください")
    ),
    category: v.picklist(["design", "engineering", "sales", "marketing"]),
    message: v.pipe(
        v.string(),
        v.minLength(1, "お問い合わせ内容は1文字以上で入力してください"),
        v.maxLength(1000, "お問い合わせ内容は1000文字以内で入力してください")
    ),
})

// 型推論
type ContactForm = v.InferOutput<typeof contactSchema>

バリデーション実行

const handleSubmit = async (data: ContactForm) => {
    // safeParseでバリデーション実行
    const result = v.safeParse(contactSchema, data)

    if (result.success) {
        // 成功時: result.outputに検証済みデータ
        console.log("Validated Data:", result.output)
    } else {
        // 失敗時: result.issuesにエラー情報
        const fieldErrors: Record<string, string> = {}
        result.issues.forEach((issue) => {
            const fieldName = issue.path?.[0]?.key as string
            if (fieldName && !fieldErrors[fieldName]) {
                fieldErrors[fieldName] = issue.message
            }
        })
        // エラー表示処理
    }
}

ArkType

スキーマ定義

import { type } from "arktype"

// TypeScriptライクな型リテラル記法
const contactSchema = type({
    company: "1<=string<=100",
    name: "1<=string<=50",
    email: "string",
    category: "'design'|'engineering'|'sales'|'marketing'",
    message: "1<=string<=1000"
})

// 型推論
type ContactForm = typeof contactBaseSchema.infer

// 詳細なバリデーションは別関数で実装
function validateContact(data: ContactForm): { valid: boolean; errors: Record<string, string> } {
    const errors: Record<string, string> = {}

    if (data.company.length === 0) {
        errors.company = "会社名を入力してください"
    } else if (data.company.length > 100) {
        errors.company = "会社名は100文字以内で入力してください"
    }

    if (data.name.length === 0) {
        errors.name = "名前を入力してください"
    } else if (data.name.length > 50) {
        errors.name = "名前は50文字以内で入力してください"
    }

    if (data.email.length === 0) {
        errors.email = "メールアドレスを入力してください"
    } else {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
        if (!emailRegex.test(data.email)) {
            errors.email = "正しいメールアドレスを入力してください"
        }
    }

    if (data.message.length === 0) {
        errors.message = "お問い合わせ内容を入力してください"
    } else if (data.message.length < 10) {
        errors.message = "お問い合わせ内容は10文字以上で入力してください"
    } else if (data.message.length > 1000) {
        errors.message = "お問い合わせ内容は1000文字以内で入力してください"
    }

    return {
        valid: Object.keys(errors).length === 0,
        errors,
    }
}

バリデーション実行

Copyconst handleSubmit = async (data: ContactForm) => {
    // Step 1: 型チェック
    const typeCheckResult = contactBaseSchema(data)

    // problemsプロパティで判定
    if ("problems" in typeCheckResult) {
        // 型エラーの処理
        const fieldErrors: Record<string, string> = {}
        (typeCheckResult as any).problems.forEach((problem: any) => {
            const pathKey = problem.path?.[0]
            if (pathKey && !fieldErrors[pathKey]) {
                fieldErrors[pathKey] = "入力値の型が不正です"
            }
        })
        // エラー表示処理
    } else {
        // Step 2: カスタムバリデーション
        const validData = typeCheckResult as ContactForm
        const validation = validateContact(validData)

        if (validation.valid) {
            console.log("Validated Data:", validData)
        } else {
            // バリデーションエラーの処理
            setErrors(validation.errors)
        }
    }
}

総括

ZodとValibotは、メソッドチェーンやパイプライン方式による直感的でシンプルな記法が特徴で、バリデーションルールが一目で理解でき、エラーハンドリングもsafeParseで統一されているため、コードの可読性と保守性に優れており、チーム開発にも適していそうです。

ArkTypeは、TypeScriptライクな型リテラル記法を採用した独特なアプローチをとっており、基本的な型チェックは簡潔に書けますが、詳細なバリデーションやエラーメッセージのカスタマイズには別途カスタム関数の実装が必要です。そのため実装コード量が増える傾向があり、独自の記法ゆえに学習コストも高そうです。

📌 個人的な所感

Zodがv4になったことで、従来課題だったパフォーマンスやバンドルサイズが大幅に改善され、継続してバリデーションライブラリの有力候補となりそうです。

また、エコシステムやコミュニティが他のライブラリと比べて圧倒的に活発で、成熟度を重視するならZodが優位になりそうです。

ただし、パフォーマンスを最優先に考える場合や、シンプルなアプリケーションの場合は、ValibotやArktypeのような特定の強みに特化したライブラリを選択する方が適している可能性があります。

個人開発でValibotを実際に導入してみましたが、使用中に困った場面や対応できないケースは特になく、中規模程度のアプリケーションであれば十分に候補として検討できると感じました。

👀 おわり

最後まで読んでくださり、ありがとうございました!☺️
この記事を通して、少しでも開発のお役に立てば幸いです!

個人ブログでも「技術選定に関すること」や「最新技術の分析・深掘り」など学びや知見を発信しています。もしご興味のある方はこちらからご確認いただけますと幸いです!
https://techbuild.app/blog

過去の執筆記事

https://zenn.dev/m_noto/articles/a73dc4291983e6
https://zenn.dev/m_noto/articles/ccd5faf08a5b63

Discussion