목차9개 항목
출처와 참고
ByteByteGo의 System Design 101 가이드 「What do version numbers mean?」을 바탕으로, 세부 규칙은 SemVer 2.0.0 명세로 확인해 재구성한 노트다. 「유의적 버전 명세 2.0.0(한국어)」의 내용을 더해 보강했다. 범위 문법과 범위가 필요한 이유는 NodeSource의 「Semver: A Primer」를 바탕으로, npm의 semver 문서로 확인해 보강했다.
SemVer란
유의적 버전(Semantic Versioning, SemVer)은 버전 번호만 보고 무엇이 바뀌었는지 알 수 있게 하는 규칙이다. 라이브러리를 쓰는 쪽은 번호를 보고 "올려도 내 코드가 안 깨지나"를 판단한다. 이 약속이 지켜져야 패키지 매니저가 업데이트 범위를 자동으로 정할 수 있다.
flowchart LR
major["2<br/>MAJOR: 하위 호환이 깨지는 API 변경"]
minor["4<br/>MINOR: 하위 호환되는 기능 추가"]
patch["1<br/>PATCH: 하위 호환되는 버그 수정"]
major ~~~ minor ~~~ patch기준은 공개 API다. 내부 구현을 아무리 많이 바꿔도 공개 API가 그대로면 MAJOR가 아니다. 반대로 한 줄을 고쳐도 기존 사용자의 코드가 깨지면 MAJOR다.
자리를 올리는 규칙
| 올리는 자리 | 언제 | 나머지 자리 |
|---|---|---|
| MAJOR | 함수 삭제·이름 변경, 인자·반환 형식 변경, 기본 동작 변경 | MINOR·PATCH를 0으로 |
| MINOR | 기존 사용을 깨지 않는 새 기능, 기능 폐기 예고(deprecation) | PATCH를 0으로 |
| PATCH | 동작을 명세대로 되돌리는 버그 수정 | 그대로 |
예시 흐름
0.1.0 초기 개발. 아무 때나 바뀔 수 있다
1.0.0 첫 안정판. 여기서부터 공개 API를 약속한다
1.0.1 1.0.0의 버그 수정
1.0.3 → 1.1.0 호환되는 새 기능 추가 (PATCH는 0으로)
1.2.2 → 2.0.0 호환이 깨지는 변경 (MINOR·PATCH는 0으로)0.x 버전은 "아직 안정 API가 없다"는 뜻이다. 0.x에서는 MINOR가 바뀌어도 호환이 깨질 수 있다. 운영에 쓰이기 시작했다면 1.0.0으로 올려 약속을 시작하는 것이 맞다.
프리릴리스와 빌드 메타데이터
| 표기 | 예 | 의미 |
|---|---|---|
| 프리릴리스 | 1.0.0-alpha, 1.0.0-beta.2, 1.0.0-rc.1 | 정식판 전의 불안정한 버전. 하이픈 뒤에 붙인다 |
| 빌드 메타데이터 | 1.0.0+20130313144700, 1.0.0+sha.5114f85 | 빌드 정보. 플러스 뒤에 붙이며 버전 비교에 쓰지 않는다 |
버전 비교는 MAJOR → MINOR → PATCH 순으로 숫자를 비교하고, 같으면 프리릴리스가 붙은 쪽이 낮다.
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0 < 1.0.1 < 1.1.0 < 2.0.0패키지 매니저의 범위 표기
npm의 package.json은 SemVer를 믿고 업데이트 범위를 적는다.
| 표기 | 허용 범위 | 뜻 |
|---|---|---|
1.2.3 | 정확히 1.2.3 | 고정 |
~1.2.3 | >=1.2.3 <1.3.0 | PATCH 업데이트만 |
^1.2.3 | >=1.2.3 <2.0.0 | MAJOR가 같으면 허용 (npm 기본) |
^0.2.3 | >=0.2.3 <0.3.0 | 0.x에서는 MINOR를 MAJOR처럼 취급 |
^0.0.3 | >=0.0.3 <0.0.4 | 0.0.x에서는 사실상 고정 |
2, 2.x, 2.* | >=2.0.0 <3.0.0 | 빈 자리는 무엇이든(X-범위) |
1.2.3 - 2.3.4 | >=1.2.3 <=2.3.4 | 하이픈 범위. 양 끝 포함 |
>=1.2.0 <1.5.0, ^1.0.0 || ^2.0.0 | 적은 그대로 | 비교 연산자를 공백(AND)과 ||(OR)로 조합 |
*, latest | 아무 버전 | MAJOR가 바뀌어도 받으므로 쓰지 않는다 |
프리릴리스 버전(1.3.0-beta.1)은 범위에 들어가도 기본으로 고르지 않는다. 범위 쪽에 같은 MAJOR.MINOR.PATCH의 프리릴리스가 적혀 있을 때만(^1.3.0-beta.0) 후보가 된다. 안정 버전을 쓰는 사용자가 실수로 베타를 받지 않게 하려는 규칙이다.
~·^ 같은 표기는 SemVer 명세가 아니라 패키지 매니저가 정한 문법이다(npm은 Ruby Bundler에서 가져왔다). 범위가 쓸모 있는 이유는 깊은 의존성의 버그 수정이 저절로 전파되기 때문이다. 내 앱 → A → B → C로 이어진 의존에서 C가 패치를 내면, 모두 ^로 적어 둔 한 A·B가 새로 게시하지 않아도 다음 설치에서 고친 C가 들어온다. 모든 의존성을 정확한 버전으로 고정하면 이 사슬이 끊긴다. 문제 있는 버전을 냈다면 패치를 낸 뒤 npm deprecate pkg@"<1.2.4" "보안 문제, 1.2.4로 올리세요"로 경고를 단다.
범위는 "호환된다고 약속된 만큼"만 열어 두는 장치다. 하지만 약속을 지키는지는 라이브러리 작성자에게 달려 있다. 그래서 잠금 파일(package-lock.json 등)로 실제 설치 버전을 고정하고, 업데이트는 테스트를 거쳐 올린다(패키지 매니저).
API 버전과의 관계
HTTP API도 같은 생각으로 버전을 관리한다. 다만 경로나 헤더에는 보통 MAJOR만 드러낸다(/v1/orders). 호환되는 변경(필드 추가)은 같은 v1 안에서 하고, 호환이 깨지는 변경만 v2로 낸다. 설계 방식은 REST API 설계에 있다.
운영 규칙
| 상황 | 규칙 |
|---|---|
| 이미 배포한 버전을 고치고 싶다 | 배포한 버전의 내용은 바꾸지 않는다. 고친 내용은 새 버전으로 낸다 |
| 호환이 깨지는 변경을 실수로 MINOR에 넣었다 | 바로 호환을 되돌린 새 버전을 내고, 문제 버전에는 경고를 붙인다(npm deprecate 등). 이미 나간 버전을 지우거나 덮어쓰지 않는다 |
| 기능을 없애고 싶다 | 먼저 폐기 예고를 담은 MINOR를 내고, 실제 제거는 다음 MAJOR에서 한다 |
| 내부 구현만 바뀌었다 | 공개 API에 영향이 없으면 호환되는 변경이다. 버그 수정이면 PATCH, 기능 추가면 MINOR |
태그를 v1.2.3으로 단다 | 흔한 태그 관례일 뿐 버전 문자열 자체는 1.2.3이다 |
프리릴리스끼리는 점으로 나눈 식별자를 앞에서부터 비교한다. 숫자만 있는 식별자는 숫자 크기로(beta.2 < beta.11), 문자가 섞이면 ASCII 순으로 비교하고, 숫자 식별자는 문자 식별자보다 낮다. 앞이 모두 같으면 식별자가 더 많은 쪽이 높다(alpha < alpha.1).
한계
- 무엇이 "호환이 깨지는 변경"인지는 사람이 판단한다. 버그에 기대던 사용자에게는 버그 수정도 깨지는 변경이다.
- 버전이 날짜·릴리스 주기와 더 관련 있는 제품(운영체제, 앱)은 날짜 기반 CalVer(
2026.10)를 쓰기도 한다.
정리
- MAJOR는 호환 깨짐, MINOR는 호환되는 기능 추가, PATCH는 호환되는 버그 수정이다.
- 기준은 공개 API다. 1.0.0부터 약속이 시작되고, 0.x는 무엇이든 바뀔 수 있다.
- 프리릴리스는
-, 빌드 메타데이터는+로 붙이고, 프리릴리스가 정식판보다 낮다. ^는 같은 MAJOR 안,~는 같은 MINOR 안의 업데이트를 허용한다.
관련 노트: 자바스크립트 패키지 매니저, The Missing Semester, REST API 설계, 커밋 컨벤션