Husky 완벽 가이드 | Git Hooks·Pre-commit·Lint-staged·실전 활용
이 글의 핵심
Husky로 Git Hooks를 관리하는 완벽 가이드. Pre-commit, Pre-push, Lint-staged, Commitlint까지 실전 예제로 정리. Husky·Git Hooks·Pre-commit 중심으로 설명합니다.
이 글의 핵심
Husky로 Git Hooks를 관리하는 완벽 가이드입니다. Pre-commit, Pre-push, Lint-staged, Commitlint까지 실전 예제로 정리했습니다.
실무 경험 공유: Husky를 도입하면서, 린트 에러가 있는 코드가 커밋되는 일이 사라지고 코드 품질이 크게 향상된 경험을 공유합니다.
들어가며: “나쁜 코드가 커밋돼요”
실무 문제 시나리오
시나리오 1: 린트 에러가 있는 코드가 푸시돼요
수동 확인은 놓치기 쉽습니다. Husky로 자동 검사합니다. 시나리오 2: 커밋 메시지가 일관적이지 않아요
규칙이 없습니다. Commitlint로 강제합니다. 시나리오 3: 테스트를 건너뛰고 푸시해요
실수로 빠뜨립니다. Pre-push Hook으로 방지합니다.
1. Husky란?
Git Hooks의 역사와 탄생 배경
Git Hooks는 Git이 처음 공개된 2005년부터 존재했던 기능입니다. .git/hooks/ 디렉터리에 셸 스크립트를 두면, 특정 Git 이벤트(커밋, 푸시, 머지 등)가 발생할 때 자동으로 실행됩니다. 초기에는 서버 관리자가 CI/CD 대신 post-receive 훅으로 자동 배포를 구현하거나, 커밋 메시지 형식을 강제하는 용도로 썼습니다.
문제는 팀 공유였습니다:
.git/hooks/는 저장소에 커밋되지 않음 (.git폴더는 Git이 관리)- 팀원마다 훅을 수동으로 설치해야 함 → 신입이 훅을 모르면 그대로 푸시
- Windows/macOS/Linux 경로·셸 차이 → 크로스 플랫폼 스크립트 작성 어려움
Husky 탄생 (2014년 Typicode 개발):
package.json으로 훅 관리 → npm install 시 자동 설치- Node.js 환경에서 실행 → 크로스 플랫폼 스크립트 작성 용이
- 팀 전체 강제 → 저장소에 커밋되므로 누구든 동일한 훅 적용
클라이언트 훅 vs 서버 훅
Git Hooks는 클라이언트 훅(로컬)과 서버 훅(원격 저장소)으로 나뉩니다.
| 훅 종류 | 실행 위치 | 용도 | Husky 지원 |
|---|---|---|---|
| 클라이언트 훅 | |||
pre-commit | 로컬 | 커밋 전 린트·테스트 | ✅ |
prepare-commit-msg | 로컬 | 커밋 메시지 템플릿 삽입 | ✅ |
commit-msg | 로컬 | 커밋 메시지 검증 | ✅ |
post-commit | 로컬 | 커밋 후 알림 | ✅ |
pre-push | 로컬 | 푸시 전 테스트·빌드 | ✅ |
| 서버 훅 | |||
pre-receive | 서버 | 푸시 거부 (권한 체크) | ❌ |
post-receive | 서버 | 배포 트리거 | ❌ |
update | 서버 | 브랜치별 정책 | ❌ |
Husky는 클라이언트 훅만 지원합니다. 서버 훅은 GitHub/GitLab의 Protected Branches·CI/CD로 대체합니다.
핵심 특징
Husky는 Git Hooks 관리 도구입니다. 주요 장점:
- 쉬운 설정: 간단한 명령어 (
npx husky init) - Git Hooks: Pre-commit, Pre-push, Commit-msg 등
- Lint-staged: 변경된 파일만 검사 (성능 최적화)
- 팀 공유: package.json으로 관리 (자동 설치)
- 크로스플랫폼: Windows, macOS, Linux (Node.js 기반)
Husky의 내부 동작
-
설치 시 (
npx husky init):.husky/폴더 생성.git/hooks/에 Husky 로더 스크립트 설치package.json의prepare스크립트에husky추가
-
커밋 시 (
git commit):1. Git이 .git/hooks/pre-commit 실행 2. Husky 로더가 .husky/pre-commit 찾아 실행 3. .husky/pre-commit에서 npx lint-staged 등 실행 4. 스크립트가 exit 0 → 커밋 진행 5. 스크립트가 exit 1 → 커밋 거부 -
팀원 설치 시 (
npm install):prepare스크립트 자동 실행 → Husky 훅 설치- 이미
.husky/파일이 있으므로 즉시 적용
2. 설치 및 기본 설정
설치
npm install -D husky
npx husky init
자동으로 .husky/ 폴더와 pre-commit 파일이 생성됩니다.
.husky/pre-commit
npm test
3. Pre-commit Hook
ESLint + Prettier
# .husky/pre-commit
npm run lint
npm run format:check
전체 파일 검사
# .husky/pre-commit
npm run lint
npm run test
4. Lint-staged
설치
npm install -D lint-staged
.husky/pre-commit
npx lint-staged
package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,md}": [
"prettier --write"
]
}
}
5. Pre-push Hook
# .husky/pre-push
npm run test
npm run build
6. Commitlint
설치
npm install -D @commitlint/cli @commitlint/config-conventional
commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'test',
'chore',
],
],
},
};
.husky/commit-msg
npx --no -- commitlint --edit $1
커밋 메시지 형식
feat: add user authentication
fix: resolve login bug
docs: update README
style: format code
refactor: simplify API logic
test: add unit tests
chore: update dependencies
7. 실전 예제: 풀 설정
package.json
{
"scripts": {
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
"format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
"test": "jest",
"prepare": "husky"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write",
"jest --bail --findRelatedTests"
],
"*.{json,css,md}": [
"prettier --write"
]
},
"devDependencies": {
"husky": "^9.0.0",
"lint-staged": "^15.0.0",
"@commitlint/cli": "^18.0.0",
"@commitlint/config-conventional": "^18.0.0"
}
}
.husky/pre-commit
npx lint-staged
.husky/pre-push
npm run test
npm run build
.husky/commit-msg
npx --no -- commitlint --edit $1
8. 실무 함정과 대응 전략
함정 1: --no-verify남용
# ❌ 나쁜 습관: "급해서" 매번 bypass
git commit --no-verify -m "fix: urgent hotfix"
git push --no-verify
문제: 훅을 무력화하면 훅을 만든 의미가 없습니다. 프로덕션에 린트 에러가 배포됩니다.
대응:
--no-verify는 정말 긴급한 상황(훅 자체 버그, 외부 서비스 장애)에만- 코드 리뷰에서
--no-verify커밋 발견 시 회고 주제로 다루기 - CI/CD에서 동일한 검사 수행 (훅 우회해도 머지 전 차단)
함정 2: 느린 훅으로 개발 흐름 끊김
# ❌ 전체 테스트 실행 (5분 소요)
# .husky/pre-commit
npm test
npm run build
문제: 커밋 한 번에 5분 대기 → 개발자가 --no-verify 쓰기 시작
대응:
# ✅ 변경된 파일만 테스트
# .husky/pre-commit
npx lint-staged
# package.json
{
"lint-staged": {
"*.{js,ts}": [
"eslint --fix",
"jest --bail --findRelatedTests" // 관련 테스트만
]
}
}
- Pre-commit: 빠른 검사 (린트, 포맷, 관련 테스트)
- Pre-push: 무거운 검사 (전체 테스트, 빌드)
함정 3: CI에서만 실패하는 코드
시나리오: 로컬 훅은 통과했는데 CI에서 실패
# 로컬: 오래된 node_modules
npm test # 통과
# CI: 깨끗한 환경
npm ci && npm test # 실패
원인: 로컬 캐시된 패키지 버전이 다름, .env 파일 차이
대응:
- CI와 로컬 훅을 동기화
# .husky/pre-commit npm ci --prefer-offline || npm install npm test - 환경 변수 검증 (
.env.example제공)
함정 4: Windows 경로 문제
# ❌ Unix 스타일 경로 (Windows에서 실패)
# .husky/pre-commit
#!/bin/sh
./scripts/lint.sh
대응:
# ✅ Node.js 스크립트 사용 (크로스 플랫폼)
# .husky/pre-commit
npx lint-staged
node scripts/validate.js
함정 5: Husky가 설치 안 됨 (신입·CI)
증상: 신입 개발자 PC에서 훅이 작동 안 함
원인:
npm install --production(devDependencies 제외).git폴더가 없는 경로에서 설치 (Docker 빌드 등)
대응:
// package.json
{
"scripts": {
"prepare": "husky || true" // 실패해도 install은 계속
}
}
- 저장소 clone 후 첫
npm install시 자동 설치 확인 - onboarding 문서에
npx husky install명시
일시적 비활성화 (정당한 사용)
# ✅ 훅 자체를 디버깅할 때
git commit --no-verify -m "debug: test hook bypass"
# ✅ 외부 서비스(lint API) 장애로 훅 실패할 때
HUSKY=0 git commit -m "fix: bypass due to linter service outage"
9. CI/CD 통합
GitHub Actions
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm run format:check
- run: npm test
정리 및 체크리스트
핵심 요약
- Husky: Git Hooks 관리
- Pre-commit: 커밋 전 검사
- Lint-staged: 변경된 파일만
- Commitlint: 커밋 메시지 규칙
- Pre-push: 푸시 전 검사
- 팀 공유: package.json
구현 체크리스트
- Husky 설치
- Pre-commit Hook 설정
- Lint-staged 설정
- Commitlint 설정
- Pre-push Hook 설정
- VS Code 통합
- 팀원과 공유
- CI/CD 통합
같이 보면 좋은 글
- ESLint 완벽 가이드
- Prettier 완벽 가이드
- Git 완벽 가이드
이 글에서 다루는 키워드
Husky, Git Hooks, Pre-commit, Lint-staged, Code Quality, Git, Frontend
내부 동작과 핵심 메커니즘
이 글의 주제는 「Husky 완벽 가이드 | Git Hooks·Pre-commit·Lint-staged·실전 활용」입니다. 여기서는 앞선 설명을 구현·런타임 관점에서 한 번 더 압축합니다. 구성 요소 간 책임 분리와 관측 가능한 지점을 기준으로 생각하면, “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)이 어디서 터지는가”가 한눈에 드러납니다.
처리 파이프라인(개념도)
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
알고리즘·프로토콜 관점에서의 체크포인트
- 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(예: 버퍼 경계, 프로토콜 상태, 트랜잭션 격리)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 동일 입력에 동일 출력이 보장되는 순수한 층과, 시간·네트워크에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합처럼 “한 번의 호출이 아니라 누적되는 비용”을 의심 목록에 넣습니다.
프로덕션 운영 패턴
실서비스에서는 기능 구현과 함께 관측·배포·보안·비용이 동시에 요구됩니다. 아래는 팀에서 자주 쓰는 최소 체크리스트입니다.
| 영역 | 운영 관점에서의 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율/지연 분위수, 주요 의존성 타임아웃이 보이는가 |
| 안전성 | 입력 검증·권한·비밀 관리가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등한 연산에만 적용되는가, 서킷 브레이커·백오프가 있는가 |
| 성능 | 캐시 계층·배치 크기·풀링·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리, 마이그레이션 호환성이 문서화되어 있는가 |
운영 환경에서는 “개발자 PC에서는 재현되지 않던 문제”가 시간·부하·데이터 크기 때문에 드러납니다. 따라서 스테이징의 데이터 양·네트워크 지연을 가능한 한 현실에 가깝게 맞추는 것이 중요합니다.
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스 컨디션, 타임아웃, 외부 의존성 불안정 | 최소 재현 스크립트 작성, 분산 트레이스·로그 상관관계 확인 |
| 성능 저하 | N+1 쿼리, 동기 I/O, 잠금 경합, 과도한 직렬화 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 클로저/이벤트 구독 누수, 대용량 객체의 불필요한 복사 | 상한·TTL·스냅샷 비교(힙 덤프/트레이스) |
| 빌드·배포만 실패 | 환경 변수·권한·플랫폼 차이 | CI 로그와 로컬 diff, 컨테이너/런타임 버전 핀(pin) |
권장 디버깅 순서: (1) 최소 재현 만들기 (2) 최근 변경 범위 좁히기 (3) 의존성·환경 변수 차이 확인 (4) 관측 데이터로 가설 검증 (5) 수정 후 회귀·부하 테스트.
자주 묻는 질문 (FAQ)
Q. 팀원도 자동으로 설정되나요?
A. 네, npm install 시 자동으로 설정됩니다.
Q. Windows에서도 작동하나요?
A. 네, 크로스플랫폼으로 작동합니다.
Q. Hook을 건너뛸 수 있나요?
A. 네, —no-verify 플래그를 사용하면 됩니다.
Q. 프로덕션에서 사용해도 되나요?
A. 네, 거의 모든 JavaScript 프로젝트에서 사용하고 있습니다.