
본 포스팅은 Conventional Commits 스펙(specification, 명세)을 처음 접하는 개발자를 위해,
개념부터 실제 툴체인(toolchain, 개발 도구들의 연결 체계) 설정까지 정리한 내용이다.
팀 프로젝트를 하거나 오픈소스 저장소를 돌아보면, 커밋 히스토리가 이런 식인 경우를 발견할 수 있다. (컨벤션이 없거나, 개인적으로 하거나...등등)
fix
update
asdf
수정
진짜 마지막 수정
읽는 사람 입장에서는 아무런 정보가 없다. 무엇을 고쳤는지, 왜 고쳤는지, 이게 기능 추가인지 버그 수정인지조차 모른다. 히스토리가 길어질수록, 팀원이 많아질수록 이 문제는 더 심각해진다.
Conventional Commits는 이 문제를 해결하기 위해 만들어진 커밋 메시지 작성 규약(convention) 이다.
Conventional Commits는 커밋 메시지에 구조화된 포맷을 적용하는 경량 스펙이다. 2017년 Angular 팀의 커밋 컨벤션에서 영감을 받아 만들어졌으며, 현재는 독립적인 스펙으로 관리되고 있다.
핵심 아이디어는 간단하다.
커밋 메시지에 타입(type) 을 명시해서, 이 변경이 무엇인지를 기계와 사람 모두가 이해할 수 있게 만들자.
2025년 Wikipedia 분석 결과에 따르면, 인기 있는 NPM 프로젝트 381개 중 약 94.5%가 Conventional Commits 형식의 커밋을 포함하고 있을 만큼 특히 JavaScript/TypeScript 생태계에서 사실상 표준으로 자리잡은 상태다.
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
각 요소를 하나씩 살펴보자.
이 커밋이 어떤 종류의 변경인지를 나타낸다.
| type | 설명 | SemVer 영향 |
|---|---|---|
feat | 새로운 기능 추가 | MINOR 버전 증가 |
fix | 버그 수정 | PATCH 버전 증가 |
docs | 문서만 변경 | 없음 |
style | 코드 동작에 영향 없는 포맷팅 (세미콜론, 공백 등) | 없음 |
refactor | 기능 추가도 버그 수정도 아닌 코드 개선 | 없음 |
test | 테스트 추가 또는 수정 | 없음 |
chore | 빌드 프로세스, 패키지 매니저 설정 등 | 없음 |
perf | 성능 개선 | 없음 |
build | 빌드 시스템 또는 외부 의존성 변경 | 없음 |
ci | CI/CD 설정 변경 | 없음 |
스펙 공식 필수 타입은 feat과 fix 두 가지뿐이다. 나머지는 Angular 컨벤션에서 가져온 권장 타입으로, 대부분의 팀이 이 세트를 함께 사용한다.
어느 모듈/영역이 변경됐는지를 괄호 안에 표시한다.
feat(auth): add OAuth2 login support
fix(user-api): handle null response from profile endpoint
docs(readme): update installation guide
scope는 팀마다 다르게 정의한다. 중요한 건 한 번 정한 scope 이름을 일관성 있게 사용하는 것이다.
변경 사항을 명령형(imperative mood) 으로, 짧게 요약한다.
# ❌ 과거형/현재진행형
fixed the login bug
fixing the login bug
# ✅ 명령형
fix login token expiry not being handled
영어로 작성하는 경우, 첫 글자는 소문자로 시작하고 마침표를 붙이지 않는 것이 일반적이다.
왜 이 변경을 했는지, 어떤 방식으로 해결했는지를 자유롭게 적는다. description과 빈 줄로 구분한다.
이슈 번호 참조, Breaking Change 명시 등에 사용한다.
하위 호환성이 깨지는 변경(Breaking Change)은 두 가지 방법으로 표시한다.
방법 1 — ! 접미사 (권장)
feat(api)!: change user ID from integer to UUID
방법 2 — footer에 BREAKING CHANGE: 명시
feat(api): change user ID from integer to UUID
BREAKING CHANGE: user ID type has changed from integer to UUID.
Existing clients must update their integration accordingly.
Breaking Change가 포함된 커밋은 MAJOR 버전을 증가시킨다. (e.g., 1.2.3 → 2.0.0)
Conventional Commits가 강력한 이유 중 하나가 바로 시맨틱 버저닝(SemVer) 과의 연계다.
SemVer는 MAJOR.MINOR.PATCH 형식의 버전 번호 규칙이다.
1.4.2
│ │ └── PATCH: 버그 수정, 하위 호환 유지
│ └──── MINOR: 새 기능 추가, 하위 호환 유지
└────── MAJOR: 하위 호환 불가능한 변경
Conventional Commits의 타입은 이 세 단계와 직접 매핑된다.
| 커밋 타입 | 버전 변화 | 예시 |
|---|---|---|
fix: | 1.0.0 → 1.0.1 | PATCH |
feat: | 1.0.0 → 1.1.0 | MINOR |
feat!: 또는 BREAKING CHANGE | 1.0.0 → 2.0.0 | MAJOR |
이 매핑 덕분에 커밋 히스토리만 보면 다음 버전이 얼마나 올라가야 하는지를 자동으로 판단할 수 있다.
단순한 것부터 복잡한 것까지 몇 가지 예시를 보자.
버그 수정 (단순)
fix(auth): resolve JWT token not refreshing on expiry
기능 추가 (scope + body)
feat(payment): add KakaoPay integration
Implements KakaoPay payment gateway using their REST API v2.
Supports single payment and subscription billing.
Breaking Change
feat(user)!: remove deprecated `username` field from response
BREAKING CHANGE: The `username` field has been removed from the User API response.
Use `email` as the unique identifier instead.
Refs: #204
문서 수정
docs(api): add rate limiting section to README
리팩토링
refactor(order): extract price calculation to separate service class
규칙을 손으로만 지키는 건 한계가 있다. 팀원 모두가 컨벤션을 지키게 하려면 자동화 도구가 필요하다.
Conventional Commits 생태계에서 자주 사용하는 도구들을 정리하면 다음과 같다.
커밋 메시지가 컨벤션을 따르는지 검사한다.
# 설치
npm install -D @commitlint/cli @commitlint/config-conventional
// commitlint.config.js
module.exports = {
// Angular 컨벤션 기반의 공식 룰셋 확장
extends: ['@commitlint/config-conventional'],
};
Git 훅(hook, 특정 Git 이벤트 발생 시 자동으로 실행되는 스크립트)을 쉽게 설정하고 팀 전체에 공유할 수 있게 해준다.
# 설치 및 초기화
npm install -D husky
npx husky init
# commit-msg 훅 추가 — 커밋 시마다 commitlint 실행
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg
이 설정이 완료되면, 컨벤션에 맞지 않는 커밋 메시지를 입력했을 때 다음과 같이 차단된다.
$ git commit -m "update stuff"
⧗ input: update stuff
✖ subject may not be empty [subject-empty]
✖ type may not be empty [type-empty]
✖ found 2 problems, 0 warnings
husky - commit-msg hook exited with code 1 (error)
타입을 까먹거나 형식이 헷갈리는 경우, 대화형 CLI로 커밋 메시지를 작성하게 도와주는 도구다.
npm install -D commitizen cz-conventional-changelog
npx commitizen init cz-conventional-changelog --save-dev --save-exact
git cz 또는 npx cz 명령어 실행 시 다음과 같은 프롬프트가 나타난다.
? Select the type of change that you're committing:
❯ feat: A new feature
fix: A bug fix
docs: Documentation only changes
style: Changes that do not affect the meaning of the code
refactor: A code change that neither fixes a bug nor adds a feature
perf: A code change that improves performance
test: Adding missing tests or correcting existing tests
VS Code를 사용한다면 Conventional Commits 확장 프로그램도 같은 역할을 GUI로 제공한다.
커밋 히스토리를 분석해서 다음 버전을 자동으로 결정하고, CHANGELOG를 생성하며, GitHub Release나 npm 배포까지 자동화하는 도구다.
feat: add dark mode support → 1.0.0 → 1.1.0 (MINOR)
fix: fix button color in dark mode → 1.1.0 → 1.1.1 (PATCH)
feat!: redesign theme API → 1.1.1 → 2.0.0 (MAJOR)
CI/CD 파이프라인(빌드, 테스트, 배포를 자동화하는 흐름)에 연결하면, main 브랜치에 머지되는 순간 릴리즈가 자동으로 실행된다.
개발자가 코드 작성
↓
git commit 실행
↓
Husky의 commit-msg 훅 트리거
↓
commitlint가 메시지 포맷 검사
↓ (통과 실패 시 커밋 차단)
커밋 저장소에 기록
↓
PR 머지 → main 브랜치 업데이트
↓
CI에서 semantic-release 실행
↓
커밋 타입 분석 → 버전 자동 결정
↓
CHANGELOG.md 자동 생성 + GitHub Release 생성
현실에서는 이미 수천 개의 커밋이 쌓인 기존 레거시 저장소에서 작업하는 경우도 많다. 이때는 다음과 같이 접근하는 것이 현실적이다.
| 도입 전 | 도입 후 | |
|---|---|---|
| 커밋 히스토리 | 의미 없는 메시지 나열 | 변경 의도가 명확히 기록됨 |
| 버전 관리 | 수동 결정, 팀원 간 의견 충돌 | 커밋 타입 기반 자동 결정 |
| CHANGELOG | 수동 작성 또는 생략 | 자동 생성 |
| 코드 리뷰 | PR 설명을 따로 봐야 함 | 커밋 메시지 자체가 문서 역할 |
처음에는 타입을 뭘 써야 할지 헷갈리고, feat과 refactor의 경계가 모호하게 느껴질 수 있다. 하지만 팀 내 기준을 Contributing 가이드에 명시하고, 린터(linter)가 형식만 잡아준다면 생각보다 빠르게 익숙해진다.
커밋 메시지 하나하나가 프로젝트의 역사가 된다. 미래의 팀원(또는 미래의 나)이 git log를 켰을 때 의미 있는 정보를 줄 수 있는 히스토리를 만드는 것, 그게 Conventional Commits의 핵심이다.