본문으로 건너뛰기

버전 관리

업데이트 — 2025년 7월 8일

zod@4.0.0npm에 배포되었습니다. 이제 패키지 루트("zod")에서 Zod 4를 내보냅니다. 다른 하위 경로는 변경되지 않았으며 앞으로도 계속 사용할 수 있습니다.

Zod 4로 업그레이드하려면 다음 명령을 실행하세요.

npm install zod@^4.0.0

Zod 4에서 사용하던 기존 가져오기("zod/v4""zod/v4-mini")는 앞으로도 계속 작동합니다. 다만 업그레이드한 뒤 원한다면 다음과 같이 가져오기 경로를 바꿀 수 있습니다.

이전이후
Zod 4"zod/v4""zod"
Zod 4 Mini"zod/v4-mini""zod/mini"
Zod 3"zod""zod/v3"

라이브러리 작성자라이브러리 작성자 가이드의 모범 사례에 따라 이미 Zod 4 지원을 구현했다면 피어 의존성 범위에 zod@^4.0.0를 포함하세요.

// package.json
{
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
}

그 밖의 코드는 변경할 필요가 없습니다. 최신 3.25.x 릴리스와 4.0.0 사이에는 코드 변경이 없었습니다. 라이브러리의 메이저 버전을 올릴 필요도 없습니다.

하위 경로 버전 관리에 대한 몇 가지 참고 사항

하위 경로 버전 관리 방식은 생태계가 호환성을 깨지 않고 업그레이드하도록 만들기 위한 필요악이었습니다. 처음부터 zod@4.0.0를 배포했다면 대부분의 라이브러리가 단순히 피어 의존성 버전을 올렸을 것이고, 생태계 전반에 "연쇄적인 버전 상승"이 일어났을 것입니다.

현재는 생태계 전반에서 Zod 4를 폭넓게 지원합니다. 완전히 수월한 마이그레이션은 없지만 우려했던 "버전 상승의 연쇄 반응"은 일어나지 않은 듯합니다. Hono, LangChain, React Hook Form 등 대부분의 라이브러리가 Zod 3과 Zod 4를 동시에 지원할 수 있었습니다. 여러 생태계 관리자는 보통 메이저 버전을 올려야 가능한 Zod 4 지원을 점진적으로 추가할 수 있어 편리했다는 의견을 직접 전해왔습니다. 요약하면 이 방식은 아주 잘 작동했습니다! Zod와 같은 제약을 받는 라이브러리는 드물지만, 규모가 큰 연관 생태계를 둔 라이브러리라면 비슷한 방식을 적극적으로 고려해 보길 권합니다.

Zod 4의 버전 관리

이 글은 사용자와 Zod 관련 라이브러리 생태계가 Zod 4로 쉽게 마이그레이션할 수 있도록 마련한 버전 관리 방식을 설명합니다.

일반적인 접근 방식:

  • Zod 4는 처음부터 npm에 zod@4.0.0로 배포하지 않습니다. 대신 하위 경로("zod/v4")를 통해 zod@3.25.0과 함께 내보냅니다.
  • 그래도 Zod 4는 안정적이며 프로덕션에서 사용할 준비가 된 버전입니다.
  • Zod 3은 패키지 루트("zod")와 새로운 하위 경로 "zod/v3"에서 계속 내보냅니다. 버그 수정과 안정성 개선도 계속 제공합니다.

이 방식은 Go가 메이저 버전 변경을 처리하는 방법과 비슷합니다: https://go.dev/doc/modules/major-version

나중에:

  • 패키지 루트("zod")에서 Zod 3 대신 Zod 4를 내보내기 시작합니다.
  • 이 시점에 zod@4.0.0를 npm에 배포합니다.
  • "zod/v4" 하위 경로는 앞으로도 계속 제공합니다.

왜?

Zod는 생태계에서 독특한 위치를 차지합니다. 많은 라이브러리와 프레임워크가 사용자가 정의한 Zod 스키마를 받습니다. 따라서 사용자 대상 API가 Zod의 여러 클래스, 인터페이스, 유틸리티와 강하게 결합됩니다. 이런 라이브러리와 프레임워크에서는 Zod의 호환성을 깨는 변경이 곧 사용자에게도 호환성을 깨는 변경이 됩니다. Zod 3의 ZodType는 Zod 4의 ZodType에 할당할 수 없습니다.

라이브러리는 왜 v3와 v4를 동시에 지원할 수 없나요?

안타깝게도 피어 의존성의 제약과 패키지 관리자마다 다른 동작 때문에 한 라이브러리의 메이저 버전 두 개를 동시에 깔끔하게 지원하기는 매우 어렵습니다.

별다른 대책 없이 zod@4.0.0를 npm에 배포했다면 AI SDK 같은 유명 라이브러리를 포함해 Zod 생태계의 대다수 라이브러리가 Zod 4를 제대로 지원하려고 새 메이저 버전을 배포해야 했을 것입니다. 그러면 생태계 전체에 "연쇄적인 버전 상승"이 일어나 막대한 불편과 작업이 발생합니다.

하위 경로 버전 관리로 이 문제를 해결할 수 있습니다. 라이브러리가 Zod 3과 Zod 4(Zod Mini 포함)를 동시에 지원할 명확한 방법을 제공합니다. "zod" 하나만 피어 의존성으로 계속 정의하면 됩니다. npm 별칭, 선택적 피어 의존성, "zod-compat" 패키지 같은 복잡한 우회책도 필요하지 않습니다.

라이브러리는 "zod" 피어 의존성의 최소 버전을 zod@^3.25.0으로 올려야 합니다. 그러면 구현에서 Zod 3과 Zod 4를 모두 참조할 수 있습니다.

import * as z3 from "zod/v3"
import * as z4 from "zod/v4"

나중에 v4 지원이 널리 보급되면 npm의 메이저 버전을 올리고 패키지 루트에서 Zod 4를 내보내 전환을 마무리합니다. (현재는 이미 완료되었습니다. 이 페이지 상단의 안내를 참조하세요.)

라이브러리가 패키지 루트가 아닌 각 버전의 하위 경로에서만 가져온다면, 메이저 버전이 올라가도 코드를 변경하지 않고 계속 작동합니다.

다소 낯선 방식으로 보일 수 있지만(적어도 Go를 사용하지 않는 사람에게는), Zod 사용자와 더 넓은 생태계의 라이브러리 모두에게 깔끔하고 점진적인 마이그레이션 경로를 제공할 수 있는 유일한 방법입니다.


이 상황에서 피어 의존성이 제대로 작동하지 않는 이유를 더 자세히 살펴보겠습니다.

Zod 스키마를 받는 acceptSchema 함수를 만들려는 라이브러리라고 가정해 보겠습니다. Zod 3과 Zod 4 스키마를 모두 받고 싶습니다. 여기서는 Zod 4가 하위 경로 없이 npm에 zod@4로 배포되었다고 가정합니다. 선택지는 다음과 같습니다.

  1. npm 별칭으로 zod@3과 zod@4를 dependencies에 함께 설치합니다. 이 방법은 작동하지만 라이브러리에 Zod 3과 Zod 4의 자체 복사본이 모두 포함됩니다. 사용자의 Zod 스키마가 라이브러리의 의존성에서 가져온 z.ZodType과 동일한 클래스의 인스턴스라는 보장도 없습니다(instanceof 검사는 실패할 가능성이 큽니다).

  2. 여러 메이저 버전을 포괄하는 피어 의존성을 사용합니다: "zod@>=3.0.0". 하지만 라이브러리를 개발할 때는 여전히 기준으로 삼을 버전 하나를 골라야 합니다. 보통 이 버전을 개발 의존성으로 설치합니다. 두 버전에서 코드가 한 글자도 다르지 않게 작동하는지 직접 꼼꼼히 확인해야 합니다. Zod 3과 Zod 4는 여러 핵심 클래스의 제네릭이 단순화되거나 달라졌으므로 이 방식으로 함께 지원할 수 없습니다.

  3. 선택적 피어 의존성을 사용합니다. 모든 플랫폼에서 런타임에 어떤 피어 의존성이 설치되었는지 안정적으로 판별하는 명확한 방법을 찾기 어려웠습니다. 온라인에서는 흔히 "try/catch 안에서 동적으로 가져와 패키지 존재 여부를 확인하라"고 하지만, 이는 백엔드 환경만 가정한 조언입니다. 프런트엔드 번들러에는 이런 기능이 없으므로 설치되지 않은 의존성을 번들링하려 하면 실패합니다. 빌드 단계에서는 코드가 try/catch 안에 있어도 소용없습니다. 게다가 같은 라이브러리의 여러 버전을 다루므로 package.json에서 두 버전을 구분하려면 npm 별칭이 필요합니다. v10처럼 최근 npm 버전도 피어 의존성과 npm 별칭의 조합을 처리하지 못합니다.

  4. zod-compat을 사용합니다. 온라인에서 종종 제안하는 이 모호한 해법은 "각 버전의 기본 기능을 나타내는 인터페이스를 정의하는 것"입니다. 즉 유틸리티 타입 모음을 만들어 실제 라이브러리의 타입을 대략 흉내 냅니다. 오류가 생기기 쉽고 작업량도 많으며 실제 구현과 계속 동기화해야 합니다. 결국 라이브러리는 세부 정보가 부족한 Zod의 모조품을 기준으로 개발하게 됩니다. 타입에만 적용할 수 있다는 한계도 있습니다. 라이브러리가 Zod 런타임 코드에 의존하면 이 방식은 무너집니다.

따라서 하위 경로.