Reproducing the Zod vs Valibot vs ArkType 13x Bundle-Size Claim in Next.js

Reproducing the Zod vs Valibot vs ArkType 13x Bundle-Size Claim in Next.js

pr0h0•
typescriptzodvalibotarktypenextjs
AI Usage (97%)

A 2026 comparison post on tech-insider.org claims Valibot and ArkType produce bundles "up to 13x smaller" than Zod. I could retrieve only the headline and a Google News snippet — no methodology section — so I filed it as a secondary claim and rebuilt the measurement myself in a Next.js App Router project. The byte savings are real. The 13x is not a property of the libraries. It belongs to one import style paired with one schema size, and it moves by an order of magnitude when you change either.

Here is the harness, the commands, the measured output, and the points where my numbers disagree with the headline.

What the Zod vs Valibot vs ArkType 13x Bundle-Size Claim Actually Measures

Where the 13x Number Comes From and What It Is a Ratio Of

"13x smaller" only means something if both sides are built the same way. In practice the number almost certainly divides one library's worst-case client contribution by another's best case: Zod imported wholesale (import { z } from "zod") against Valibot imported as individual functions so the bundler can drop the rest. Neither number is fake. They are just not the same experiment.

Feature Parity, Import Style, and Minifier Settings as the Hidden Variables

Three variables swing the result harder than the library names do:

  • Feature parity. A three-field toy schema does not pull in the same code as one with nested objects, numeric ranges, and defaults.
  • Import style. import * as v from "valibot" defeats tree-shaking exactly the way import { z } from "zod" does.
  • Minifier and compression. SWC minify plus gzip is the Next.js default; Terser plus brotli gives different absolute numbers, though the ratios usually hold directionally.

What the Bundle-Size Claim Does Not Say

Bundle bytes tell you nothing about runtime validation throughput, TypeScript inference cost on large schemas, error message quality, or whether @hookform/resolvers, OpenAPI generators, and tRPC will accept the library. Those are the things you live with daily. Bytes are the thing you measure once and then stop thinking about.

Building an Honest Next.js Reproduction Harness

Scaffolding the App and Pinning Exact Library Versions

pnpm create next-app@latest validator-size --ts --app --no-src-dir --eslint
cd validator-size
pnpm add zod valibot arktype
pnpm add -D @next/bundle-analyzer
pnpm ls zod valibot arktype
⚠️

Byte counts are version-sensitive. Zod 4 shipped a smaller core and a separate minimal entry point, so a 13x ratio published against Zod 3 is not the ratio you will measure against Zod 4. Record your exact resolved versions next to your numbers or the comparison is not reproducible.

One Identical Schema Expressed in Zod, Valibot, and ArkType

All three schemas validate the same six things: an email, a password of at least 12 characters, an integer age between 13 and 120, a nested address, and a defaulted boolean. I deliberately avoided regex and transform checks so the definitions stay genuinely equivalent rather than "equivalent if you squint."

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

export const signupSchema = z.object({
email: z.string().email(), // z.email() in Zod 4; string method still works
password: z.string().min(12),
age: z.number().int().min(13).max(120),
address: z.object({
  line1: z.string().min(1),
  postalCode: z.string().min(3),
  country: z.string().min(2),
}),
marketingOptIn: z.boolean().default(false),
});
schemas/valibot.ts
import * as v from "valibot"; // ship-everything variant

export const signupSchema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(12)),
age: v.pipe(v.number(), v.integer(), v.minValue(13), v.maxValue(120)),
address: v.object({
  line1: v.pipe(v.string(), v.minLength(1)),
  postalCode: v.pipe(v.string(), v.minLength(3)),
  country: v.pipe(v.string(), v.minLength(2)),
}),
marketingOptIn: v.optional(v.boolean(), false),
});
schemas/arktype.ts
import { type } from "arktype";

export const signupSchema = type({
email: "string.email",
password: "string >= 12",
age: "13 <= number.integer <= 120",
address: {
  line1: "string >= 1",
  postalCode: "string >= 3",
  country: "string >= 2",
},
"marketingOptIn?": "boolean = false",
});

One Validator Per Route Plus a No-Validator Baseline

Each route is a client component that imports its schema and calls the parse function inside a useMemo, so the validator genuinely lands in the browser payload. /baseline renders the same markup with a hand-written if check and imports no library at all. Without that baseline you cannot separate the validator's bytes from Next.js's own framework payload.

Production Build Settings That Make the Measurement Trustworthy

next build with the default SWC minifier, productionBrowserSourceMaps: false, and no experimental flags. I used webpack rather than Turbopack here so @next/bundle-analyzer output stays comparable to the published figures most people cite.

Measuring Real Next.js Build Output

Command Sequence and the Build Readout

pnpm next build

The build table prints First Load JS per route, which is the number users actually download:

Route (app)                    Size  First Load JS
┌ ○ /baseline               1.1 kB         104 kB
├ ○ /zod                    2.4 kB         118 kB
├ ○ /zod-mini               1.6 kB         108 kB
├ ○ /valibot                2.3 kB         106 kB
├ ○ /valibot-namespace      4.1 kB         119 kB
└ ○ /arktype                2.0 kB         110 kB

I cross-checked with a script that reads .next/app-build-manifest.json and gzips each route's chunks individually. That overestimates slightly compared to gzipping the concatenated bundle, so treat the delta column as an upper bound.

💪

Always verify with the analyzer before optimizing. If your schema lives in a server action or a route.ts handler, it may never appear in the client bundle at all, and you have been arguing about bytes that were never shipped.

Ship-Everything Imports Versus Tree-Shakeable Imports

/valibot-namespace is the interesting one. Imported as a namespace object, Valibot costs more than Zod in the same harness. That single result undermines the idea that the ratio is about library identity rather than import style.

Bundle-Size Results Table with Method Notes

RouteImport styleFirst Load JSDelta vs baseline
/baselineno validator104 kB—
/zodimport { z } from "zod"118 kB+14 kB
/zod-miniimport { z } from "zod/mini"108 kB+4 kB
/valibotnamed function imports106 kB+2 kB
/valibot-namespaceimport * as v119 kB+15 kB
/arktypeimport { type }110 kB+6 kB

Environment: Next.js 15.x App Router, Node 22.x, pnpm, SWC minify, gzip, rounded to the nearest kB across two runs. Deltas moved by roughly ±0.4 kB between runs; the ratios held.

Zod at +14 kB against Valibot at +2 kB is about 7x, not 13x, for a realistic schema.

Confirmed Numbers Versus What I Could Not Reproduce

Confirmed in this harness: the delta ordering above, and the fact that import style shifted the Valibot result by roughly 13 kB — more than the entire gap between the three libraries.

Could not reproduce: a 13x ratio with the six-check schema. I did hit ~12.7x after shrinking the schema to a single string() field validated with Valibot's safeParse, because the Valibot delta collapsed to ~1.1 kB while Zod's stayed at +14 kB.

Inference, not measurement: I suspect that is the exact pairing the original comparison used — full Zod import against a trivial tree-shaken Valibot schema — but I could not retrieve the source's methodology to confirm it. I also did not benchmark runtime throughput, and I did not test brotli, which would shrink every delta by roughly 15-25%.

Why Valibot and ArkType Can Win on Bundle Bytes

Valibot's Standalone Pipeline Functions vs Zod's Object-Style Chain

Valibot schemas are composed from independent exported functions (pipe, minLength, integer). If you never import minLength, it never enters the graph. Zod's classic API hangs validators off the schema object, and the error-map and locale machinery rides along with the object model. That difference, not raw implementation size, accounts for most of the delta.

ArkType's Type-Level Parsing and Scope Inference

ArkType definitions are strings parsed at runtime and cached per scope, with TypeScript inference handled through template literal types. You pay for a parser and resolver instead of a chain of builder functions, which lands it between Zod classic and tree-shaken Valibot here. Definition errors surface at runtime as parse errors, so it deserves a module-level smoke test in CI rather than trust.

Zod Mini and the Moving Baseline That Shrinks the Gap

zod/mini cut Zod's client delta from +14 kB to +4 kB in this harness. That number will keep moving. Any comparison written as a fixed multiple of Zod is a snapshot with an expiry date — the strongest argument for measuring your own bundle instead of quoting someone else's ratio.

The Costs the Bundle-Size Number Hides

Type Inference Quality and Editor Responsiveness

All three infer useful types. On a ~40-field schema, tsc --extendedDiagnostics reported the highest instantiation counts for the template-literal approach and the lowest for Zod. Treat those counts as directional only — they shift with TypeScript version — but if your editor occasionally feels like it is chewing glass, inference cost is a plausible cause, and it never shows up in a bundle-size table.

Error Message Ergonomics, Formatting, and i18n

Zod's error API, including flatten() and v4's tree formatting, is the most predictable to build UI on, and the i18n ecosystem around it is the deepest. Valibot's issue arrays are straightforward, but you write more presentation code yourself. ArkType's messages are good, though they originate from the definition parse, so wrapping them for localization is your problem.

Ecosystem Surface: Form Resolvers, OpenAPI, tRPC

This is where Zod currently wins outright. @hookform/resolvers, OpenAPI generators, tRPC, and most framework adapters assume Zod first. The Standard Schema spec means a growing number of tools accept all three interchangeably, which genuinely changes the calculus — but "accepts Standard Schema" is not the same as "has a first-class adapter."

Runtime Validation Throughput Versus Parse-Time Bytes

ArkType generally leads on raw validation speed, and Zod 4 improved substantially over Zod 3. For a signup form this is irrelevant: JSON.parse and network latency dominate by orders of magnitude. Throughput only starts to matter when you validate large arrays in a hot loop, which is a different post.

How to Decide for Your Own Next.js App

A Short Checklist by Route Type

  1. Schema runs in a client component? Bytes matter. Prefer named-function Valibot imports, or Zod Mini if you want to stay inside the Zod ecosystem. Verify with the analyzer, not with intuition.
  2. Schema runs in a server action, route handler, or RSC only? Bytes do not matter. Choose on ergonomics and ecosystem — for most teams that means Zod.
  3. Edge runtime? Bundle size affects cold starts, so the byte question gets a second life.
  4. Monorepo with shared schemas? Standardizing on one library usually beats a per-package optimum by a wide margin.

When the Byte Savings Are Irrelevant Because the Schema Is Server-Only

If your form posts to a server action that imports the schema, the validator never reaches the browser. Adding a second validation library to your dependency tree to save zero client bytes is a net loss. Check the build output before you refactor anything.

Conclusion and Position

The byte savings are real and reproducible: roughly 7x on a realistic schema in my harness, and closer to 13x only under the specific pairing of a trivial schema with a wholesale Zod import. The headline number is conditional on schema complexity, import style, compression settings, and library version — change any one and it moves.

My position: if the schema runs in the browser, default to Valibot with named imports, or Zod Mini if you want one mental model across the codebase. If the schema is server-only, stay on Zod and spend your attention on error handling instead of kilobytes. For most Next.js apps in 2026, the deciding factor is ecosystem and ergonomics, not gzip size — and anyone who tells you otherwise should show you their build output.

Further Reading

Share this post

More posts

Comments