본문 바로가기
Frontend2026년 8월 23일7분 읽기

Valibot — Zod보다 가벼운 스키마 검증 라이브러리

YS
김영삼
조회 6
Valibot — Zod보다 가벼운 스키마 검증 라이브러리

Valibot은 TypeScript용 스키마 검증 라이브러리로, Zod와 하는 일은 거의 같지만 번들 크기를 크게 줄인 대안이다. 핵심은 "모듈러 설계" — 검증 함수들을 개별 임포트하도록 만들어서, 트리 셰이킹으로 실제 쓰는 것만 번들에 들어간다. 그래서 기본 사용 시 최소 수 KB 수준까지 내려간다.

나는 폼 검증에 오래 Zod를 써왔는데, 클라이언트 번들에 스키마 검증 코드가 통째로 실리는 게 늘 걸렸다. 특히 엣지 함수나 가벼운 위젯에서는 이 무게가 은근히 아팠다. Valibot으로 옮기고 나서 번들 분석 그래프에서 검증 라이브러리 덩어리가 눈에 띄게 얇아졌다.

API가 다르게 생겼다 — 파이프 방식

Zod는 메서드 체이닝(z.string().email().min(5))이다. Valibot은 검증 규칙을 pipe로 나열한다. 처음엔 낯설지만, 함수 조합이라 트리 셰이킹이 잘 되는 구조다.

import * as v from "valibot";

const LoginSchema = v.object({
  email: v.pipe(v.string(), v.email("이메일 형식이 아니에요")),
  password: v.pipe(
    v.string(),
    v.minLength(8, "8자 이상"),
  ),
});

// 검증
const result = v.safeParse(LoginSchema, {
  email: "hi@test.com",
  password: "12345678",
});

if (result.success) {
  console.log(result.output); // 타입 추론된 결과
} else {
  console.log(result.issues); // 에러 목록
}

스키마에서 타입을 뽑는 것도 Zod와 비슷하다. v.InferOutput<typeof LoginSchema>로 결과 타입을, v.InferInput으로 입력 타입을 얻는다.

Zod와 나란히 비교

항목ZodValibot
API 스타일메서드 체이닝함수 + pipe
트리 셰이킹제한적함수 단위로 강함
번들 크기상대적으로 큼쓰는 만큼만, 매우 작음
생태계 성숙도넓고 풍부빠르게 성장 중

둘 다 2025년에 자리 잡은 Standard Schema 스펙을 지원한다. 덕분에 React Hook Form 같은 라이브러리는 "Zod냐 Valibot이냐"를 신경 쓰지 않고 표준 인터페이스로 받아들일 수 있다. 즉 검증 라이브러리를 갈아끼우기가 예전보다 훨씬 쉬워졌다.

변환과 커스텀 규칙

입력을 변환하고 싶을 때는 transform을 파이프에 끼운다. 문자열로 들어온 숫자를 실제 숫자로 바꾸는 흔한 케이스는 이렇게 처리한다.

const AgeSchema = v.pipe(
  v.string(),
  v.transform(Number),
  v.number(),
  v.minValue(0, "음수는 안 돼요"),
);

v.parse(AgeSchema, "42"); // => 42 (number)

커스텀 검증은 v.check로 넣는다. 예를 들어 비밀번호 확인 필드가 일치하는지 같은 규칙은 객체 스키마에 check를 붙여 처리하면 된다.

언제 Zod를 그대로 두는 게 나을까

솔직히 이미 Zod로 잘 돌아가는 서버 프로젝트라면 굳이 옮길 이유는 크지 않다. 서버는 번들 크기가 사용자 체감에 직접 닿지 않기 때문이다. Valibot이 진짜 빛나는 곳은 클라이언트 번들에 검증 코드가 실리는 프론트엔드, 특히 엣지 런타임이나 작은 위젯이다. 나는 서버는 Zod, 브라우저로 나가는 폼은 Valibot으로 나눠 쓰기도 한다.

배열, 유니온, 옵셔널 다루기

실무 스키마는 단순 문자열 검증으로 안 끝난다. 배열, 유니온, 옵셔널 필드가 섞이기 마련이다. Valibot도 이런 조합을 함수로 표현한다. 아래는 상품 목록을 검증하는 예시다.

const ProductSchema = v.object({
  name: v.pipe(v.string(), v.minLength(1)),
  status: v.picklist(["active", "hidden"]), // 유니온
  tags: v.array(v.string()),                // 배열
  discount: v.optional(v.number()),         // 있어도 되고 없어도 됨
});

const ListSchema = v.array(ProductSchema);

picklist는 Zod의 z.enum에 해당하고, 값 몇 개 중 하나를 강제할 때 쓴다. optional은 필드가 아예 없어도 통과시키되 값이 있으면 내부 스키마로 검증한다. 기본값이 필요하면 v.optional(v.number(), 0)처럼 두 번째 인자로 기본값을 줄 수 있다.

비동기 검증(예: 이메일 중복 확인)이 필요하면 parseAsynccheckAsync를 쓴다. 동기 API와 이름 규칙이 일관돼서, 서버 쪽에서 DB를 두드리는 규칙과 클라이언트 쪽 순수 검증을 같은 스키마 스타일로 유지할 수 있다는 점이 마음에 든다.

자주 묻는 질문

Valibot이 Zod보다 항상 빠른가요?

실행 속도 자체보다 번들 크기 이점이 핵심입니다. 모듈러 설계 덕에 실제 사용한 검증 함수만 번들에 포함되어 클라이언트로 내려가는 코드가 작아집니다. 런타임 성능은 스키마 복잡도에 따라 다르므로, 속도가 절대적으로 필요하면 실제 데이터로 벤치마크하는 것을 권합니다.

React Hook Form과 함께 쓸 수 있나요?

가능합니다. @hookform/resolvers가 Valibot 리졸버를 제공하며, Standard Schema 지원 덕에 통합이 매끄럽습니다. 스키마를 리졸버에 넘기면 폼 검증과 타입 추론을 그대로 받습니다.

Zod에서 Valibot으로 옮기기 어렵나요?

개념은 거의 1:1로 대응되지만 API 표기가 다릅니다. 메서드 체이닝을 pipe 나열로 바꾸고, z.infer를 v.InferOutput으로 교체하는 식입니다. 스키마가 많으면 손이 가지만, 규칙 대응표만 만들어두면 기계적으로 옮길 수 있습니다.

parse와 safeParse 중 무엇을 써야 하나요?

검증 실패 시 예외를 던져도 되는 곳(신뢰된 내부 데이터)에서는 parse가 간결하고, 사용자 입력처럼 실패가 정상 흐름인 경우에는 예외 대신 result 객체를 돌려주는 safeParse가 안전합니다. 폼 검증에는 보통 safeParse를 씁니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록