📋 目錄
看懂 Zod 4 的官方數據應該怎麼解讀,並用一份可執行的清單把 Zod 3 專案穩定升級。
前言
Zod 4 發布後,大家最常引用的關鍵字是「14x 更快」和「體積變小」。但在實務上,這兩句話如果沒有上下文,很容易被誤用成「所有解析都 14x」或「整個套件永遠小一半」,最後導致技術決策變成口號。
這篇文章的重點不是幫 Zod 4 做行銷,而是把官方資料拆回工程語言:哪些數字來自哪一種 benchmark、哪些只代表 core bundle 的最小案例、哪些 migration 變更真的會影響既有專案。
如果你正在考慮升級,或正在 review 團隊的升級 PR,本文可當作一份「快速核對清單 + 範例模板」。
Zod 4 的效能數字要怎麼看:benchmark 先分場景
官方 release notes 內的效能數字分別來自不同測試場景,不能直接合併成一句「全面 14x」。你可以先抓三個最重要的基準:
z.string().parse:約14.71x(相對 Zod 3)z.array()parsing:約7.43xz.object().safeParse:約6.5x
這三個數字本身都成立,但它們描述的是不同資料型態與不同 API(parse vs safeParse)下的結果。
實務上比較安全的寫法是:
- 字串解析在官方基準中提升幅度最大(約 14.71x)。
- 陣列與物件場景也有顯著改善(約 7.43x、6.5x)。
- 真實專案效能仍取決於 schema 複雜度、資料形狀與錯誤分支比例。
下面是你可以在本地做的「最小可重現測試」範例,避免只看網路數字:
npm i zod@4 tinybench tsx -D
// scripts/bench-zod-parse.ts
import { Bench } from "tinybench";
import * as z from "zod";
const UserSchema = z.object({
id: z.string(),
email: z.email(),
roles: z.array(z.string()),
profile: z.object({
displayName: z.string().min(2),
age: z.number().int().nonnegative(),
}),
});
const goodPayload = {
id: "u_123",
email: "dev@example.com",
roles: ["admin", "editor"],
profile: { displayName: "Owl", age: 28 },
};
const bench = new Bench({ time: 1000 });
bench
.add("parse", () => {
UserSchema.parse(goodPayload);
})
.add("safeParse", () => {
UserSchema.safeParse(goodPayload);
});
await bench.warmup();
await bench.run();
console.table(
bench.tasks.map((task) => ({
name: task.name,
hz: task.result?.hz,
meanMs: task.result ? task.result.mean * 1000 : null,
}))
);
npx tsx scripts/bench-zod-parse.ts
這樣你可以用自己的 schema 做 A/B,比只引用單一官方圖表更接近決策需求。
體積變小不是萬靈丹:先理解「core bundle benchmark」
Zod 4 常被引用的另一組數字是:
- Zod 3:
12.47kb(gzip) - Zod 4:
5.36kb(gzip) - Zod Mini:
1.88kb(gzip)
這組數據來自官方用 z.boolean() 這種極簡腳本搭配 rollup 的 core bundle benchmark。它的價值是:幫你比較「最基本情境下的核心成本」,而不是代表所有專案打包後都會固定落在這三個數字。
換句話說,你可以把它理解成「起跑線比較」,不是「最終成績保證」。
什麼情況該考慮 Zod Mini
官方文件對 Zod Mini 的定位很清楚:它功能等價,但 API 偏 functional,目標是更容易 tree-shake。這對前端 bundle 敏感場景有價值,但 DX(可讀性、可發現性)通常不如標準 Zod 直覺。
最常見的決策準則:
- 先用標準
zod開發。 - 實測發現 bundle 預算壓力確實存在(例如 web vitals、低速網路目標市場)。
- 再評估把高流量前端入口改為
zod/mini。
Zod 與 Zod Mini API 差異:重點在 .check()
Zod Mini 不是「小一點的 Zod 方法鏈」,而是把很多驗證條件改成函式傳入 .check()。
// regular zod
import * as z from "zod";
const LoginSchema = z.object({
email: z.email(),
password: z.string().min(12).max(128).trim(),
});
// zod mini
import * as z from "zod/mini";
const LoginSchema = z.object({
email: z.email(),
password: z
.string()
.check(
z.minLength(12), // 最短 12 字元
z.maxLength(128), // 最長 128 字元
z.trim() // 解析前先去除首尾空白
),
});
Zod Mini 仍保留相同的 parsing methods(parse、safeParse、parseAsync、safeParseAsync),所以你可以沿用既有資料流;真正要適應的是 schema 定義風格。
這也是為什麼在大型團隊裡,常見做法不是「全案一口氣改 Mini」,而是只在對 bundle 最敏感的頁面或 package 試點。
遷移重點:這些才是 Zod 3 -> 4 真正常踩雷的地方
舊文最常見的誤解是把 z.infer 當成被淘汰語法。實際上,Zod 4 仍可正常使用 z.infer。升級時更該優先檢查的是下列 breaking/deprecation:
- 統一
error參數(過去errorMap改名為error)。 z.record()單參數形式移除,需明確給 key/value schema。ZodObject.merge()deprecated,改用extend()。.nonempty()在 array 的型別推斷改變(行為接近.min(1),不再推成 tuple-like)。- 字串格式 API(如
email)移到 top-levelznamespace,較利於 tree-shaking。
遷移對照範例
import * as z from "zod";
// 1) z.record: 單參數不再支援
// Zod 3: z.record(z.string())
const HeadersSchema = z.record(z.string(), z.string());
// 2) .merge() -> .extend()
const BaseUser = z.object({ id: z.string() });
const WithProfile = z.object({ displayName: z.string() });
const UserSchema = BaseUser.extend(WithProfile.shape);
// 3) string format top-level API
const ContactSchema = z.object({
email: z.email(),
website: z.url(),
});
// 4) z.infer 仍可用
type User = z.infer<typeof UserSchema>;
建議的升級流程(團隊版)
# 1. 升級套件
pnpm add zod@^4
# 2. 先找明確會動到的 API
rg -n "errorMap|\.merge\(|z\.record\([^,)]*\)|\.nonempty\(" src
# 3. 跑型別檢查與測試
pnpm tsc --noEmit
pnpm test
# 4. 再跑實際資料流測試(API contract / form submission)
如果你們是 monorepo,建議先挑一個 schema 依賴最重、但業務風險中等的 package 做 pilot,讓 migration pattern 成熟後再推廣。
什麼情境值得升級、什麼情境可以先觀望
優先升級的情境
- API gateway / BFF 有大量 runtime validation。
- 前端表單量大,且 parse/safeParse 是 request path 熱點。
- 你正好要做 schema 層重構(一次處理技術債成本最低)。
可以先觀望的情境
- 目前 Zod 3 非瓶頸,且團隊短期沒有 refactor capacity。
- 你們對 bundle size 並不敏感(例如純後端、內網工具)。
- 專案正在大改版上線窗口,無法承受驗證層變更風險。
工程決策不一定要「第一天就上最新」,但建議至少做一次 PoC,用你自己的數據決定是否升級。
常見問題 / 注意事項
z.infer 在 Zod 4 還能用嗎?
可以。z.infer 仍是官方文件常見用法,並非淘汰 API。
Zod Mini 會讓所有專案都更快嗎?
不一定。它主要優勢是更容易 tree-shake 的 bundle 成本,不等於 runtime validation 一定更快。
我可以直接把所有 .email() 換成 z.email() 嗎?
建議逐步替換並配合型別測試。Zod 4 的 top-level string format API 是推薦方向,但大型專案應避免一次大改。
遷移時最容易漏掉什麼?
通常是 z.record() 單參數用法與 .merge(),因為舊程式可讀性高、不容易第一眼看出是升級風險點。
總結
Zod 4 的價值可以濃縮成三句話:
- 官方 benchmark 顯示明顯效能提升,但要按場景解讀,不要泛化成單一倍率。
- bundle size 改善成立,但請用「core bundle benchmark」理解它,而不是當成所有專案最終體積。
- 遷移重點不在
z.infer,而在error參數統一、z.record()、.merge()、.nonempty()與 string format API 調整。
如果你要把這次升級做得穩,最有效的做法是:先跑小範圍 PoC、建立遷移對照規則,再批次推進到整個專案。這比一次全面替換更可控,也更容易讓團隊接受。