기본 패턴
# 1. husky 설치 및 초기화
npm install --save-dev husky
npx husky init
# 2. pre-commit 훅 추가 (lint-staged 실행)
echo "npx lint-staged" > .husky/pre-commit
# 3. lint-staged 설정 (package.json)
# "lint-staged": {
# "*.{ts,tsx}": ["eslint --fix", "prettier --write"]
# }
# 4. commit-msg 훅으로 메시지 형식 강제
echo 'npx commitlint --edit "$1"' > .husky/commit-msg설명
Git 훅은 .git/hooks/의 셸 스크립트다 — 로컬 전용이라 팀 공유가 불가능하다
Git은 특정 이벤트 발생 시 자동으로 실행할 스크립트를 .git/hooks/ 디렉터리에서 찾는다. pre-commit, commit-msg, pre-push 등 이벤트 이름과 일치하는 실행 파일이 있으면 Git이 직접 호출한다. 문제는 .git/ 디렉터리가 버전 관리 대상이 아니라는 점이다. git clone을 해도 훅은 복사되지 않으므로, 팀원마다 직접 설치해야 한다. 이 한계를 극복하기 위해 husky나 lefthook 같은 도구가 등장했다. 이 도구들은 훅 스크립트를 프로젝트 루트의 별도 디렉터리(.husky/)에 보관하고 npm install 시 자동으로 Git 훅 경로를 재지정(core.hooksPath)하는 방식으로 팀 전체가 동일한 훅을 공유하게 만든다.
# 수동으로 훅 경로를 직접 지정하는 방식 (도구 없이)
git config core.hooksPath .githooks
chmod +x .githooks/pre-commitpre-commit은 커밋 직전에 실행되어 exit code로 커밋을 통과/차단한다
pre-commit 훅은 git commit 명령 직후, 커밋 객체가 생성되기 직전에 실행된다. 스크립트가 exit code 0을 반환하면 커밋이 진행되고, 0이 아니면 커밋이 중단된다. 이 시점에 린트, 포맷터, 단위 테스트를 실행하면 규칙을 어긴 코드가 저장소에 들어오는 것을 막을 수 있다. 그러나 pre-commit에서 전체 파일을 검사하면 속도가 느려진다. lint-staged를 함께 쓰면 스테이징된 파일만 검사해 실행 시간을 크게 줄인다.
# .husky/pre-commit
#!/bin/sh
npx lint-staged
# package.json lint-staged 설정
# "lint-staged": {
# "*.{js,ts,tsx}": [
# "eslint --fix --max-warnings=0",
# "prettier --write"
# ],
# "*.{css,scss}": ["stylelint --fix"]
# }husky + lint-staged로 훅을 버전 관리하는 표준 패턴
husky는 package.json의 prepare 스크립트를 통해 npm install 시 자동으로 Git 훅 경로를 설정한다. .husky/ 디렉터리 안의 훅 파일을 git으로 추적하면 팀원이 저장소를 클론하고 npm install만 실행해도 훅이 활성화된다. lefthook은 단일 YAML 파일로 훅을 정의하고 병렬 실행을 지원해 속도가 빠르며, Node.js 없이도 동작하는 바이너리를 제공한다.
# husky 설치 흐름 (Node.js 프로젝트)
npm install --save-dev husky lint-staged
npx husky init
# → .husky/pre-commit 생성, package.json에 prepare 스크립트 추가
# package.json 결과
# {
# "scripts": {
# "prepare": "husky"
# }
# }
# lefthook 예시 (lefthook.yml)
# pre-commit:
# parallel: true
# commands:
# lint:
# glob: "*.{ts,tsx}"
# run: npx eslint {staged_files}
# format:
# glob: "*.{ts,tsx,css}"
# run: npx prettier --write {staged_files}commit-msg 훅으로 Conventional Commits 형식을 강제한다
commit-msg 훅은 사용자가 메시지를 작성한 직후 실행된다. 첫 번째 인수로 임시 커밋 메시지 파일 경로가 전달된다. commitlint와 연동하면 feat:, fix:, chore: 등 Conventional Commits 형식에서 벗어난 메시지를 가진 커밋을 자동으로 거부할 수 있다. CHANGELOG 자동 생성과 시맨틱 버전 자동화를 위한 전제 조건이기도 하다.
# 패키지 설치
npm install --save-dev @commitlint/cli @commitlint/config-conventional
# commitlint.config.js
# module.exports = { extends: ['@commitlint/config-conventional'] };
# .husky/commit-msg
#!/bin/sh
npx commitlint --edit "$1"
# 통과하는 커밋 메시지 예시
# feat: 로그인 폼 유효성 검사 추가
# fix: 사용자 프로필 이미지 로딩 오류 수정
# chore: 의존성 패키지 업데이트
# 거부되는 메시지 예시
# 로그인 고침 ← type 없음
# Update stuff ← 형식 불일치빠른 정리
| 상황 | 적합한 선택 |
|---|---|
| 커밋 전 린트·포맷 자동 실행 | pre-commit 훅 + lint-staged |
| 커밋 메시지 형식 강제 | commit-msg 훅 + commitlint |
| push 전 전체 테스트 실행 | pre-push 훅 + npm test |
| 팀 전체에 훅 배포 | husky 또는 lefthook (버전 관리) |
| 훅 실행 없이 긴급 커밋 | git commit --no-verify (CI에서 반드시 재검사) |
주의할 점
git commit --no-verify로 모든 훅을 우회할 수 있습니다. 훅은 개발자 편의를
위한 로컬 보조 수단이지 보안 장치가 아닙니다.
# ❌ 훅에만 의존하면 우회된 커밋이 그대로 배포될 수 있다
git commit --no-verify -m "fix: 급하게 배포"
# ✅ CI 파이프라인에서 동일한 검사를 반드시 중복 실행한다
# .github/workflows/ci.yml
# - run: npx eslint .
# - run: npx tsc --noEmit
# - run: npm test훅은 "실수로 잘못된 코드를 커밋하는 것"을 방지하는 용도입니다. 의도적 우회를 막으려면 CI/CD에서 동일 검사를 필수로 실행해야 합니다.