본문으로 건너뛰기
Previous
Next
Husky 완벽 가이드 | Git Hooks·Pre-commit·Lint-staged·실전 활용

Husky 완벽 가이드 | Git Hooks·Pre-commit·Lint-staged·실전 활용

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의 내부 동작

  1. 설치 시 (npx husky init):

    • .husky/ 폴더 생성
    • .git/hooks/ 에 Husky 로더 스크립트 설치
    • package.jsonprepare 스크립트에 husky 추가
  2. 커밋 시 (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 → 커밋 거부
  3. 팀원 설치 시 (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 통합

같이 보면 좋은 글


이 글에서 다루는 키워드

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 프로젝트에서 사용하고 있습니다.