본문으로 건너뛰기 Husky Complete Guide | Git Hooks for Linting· Formatting

Husky Complete Guide | Git Hooks for Linting· Formatting

Husky Complete Guide | Git Hooks for Linting· Formatting

이 글의 핵심

Husky runs scripts automatically on git events — pre-commit linting catches errors before they enter your repo, commitlint enforces commit message standards, and pre-push hooks prevent broken code from reaching CI. Set it up once and enforce quality for the whole team.

What Husky Does

Husky makes git hooks easy:

git commit → pre-commit hook → lint + format staged files
git commit → commit-msg hook → validate commit message format
git push   → pre-push hook  → run tests

Without Husky, hooks are per-developer local files (.git/hooks/) not shared in the repo. With Husky, they’re in package.json and installed automatically.


1. Installation

# Install Husky
npm install --save-dev husky

# Initialize (creates .husky/ directory + adds prepare script)
npx husky init

This adds to package.json:

{
  "scripts": {
    "prepare": "husky"
  }
}

And creates .husky/pre-commit with a sample hook.


2. Pre-commit Hook — Lint and Format

lint-staged runs linters only on staged files — much faster than linting everything.

npm install --save-dev lint-staged
// package.json
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{css,scss,json,md}": [
      "prettier --write"
    ]
  }
}
# .husky/pre-commit
npx lint-staged

Now every git commit automatically:

  1. Finds staged .ts/.tsx/.js/.jsx files
  2. Runs ESLint with auto-fix
  3. Runs Prettier
  4. Re-stages the fixed files
  5. Commits

Alternative: direct commands

# .husky/pre-commit
npm run lint
npm run format:check

3. Commit Message Validation — commitlint

Enforce conventional commit format: feat(scope): description

npm install --save-dev @commitlint/cli @commitlint/config-conventional
// commitlint.config.js
export default {
  extends: ['@commitlint/config-conventional'],
  rules: {
    // Customize if needed
    'type-enum': [2, 'always', [
      'feat',    // new feature
      'fix',     // bug fix
      'docs',    // documentation
      'style',   // formatting
      'refactor',// code change without feat/fix
      'test',    // tests
      'chore',   // build/tooling
      'perf',    // performance
      'ci',      // CI changes
      'revert',  // revert commit
    ]],
    'subject-max-length': [2, 'always', 100],
  },
}
# .husky/commit-msg
npx --no -- commitlint --edit $1

Valid commit messages:

feat(auth): add OAuth2 login
fix(api): handle null response from user endpoint
docs: update README installation steps
chore: upgrade dependencies to latest

Invalid (will be rejected):

WIP
update stuff
Fixed the bug

4. Pre-push Hook — Run Tests

Pre-commit should be fast (lint only). Run tests before push:

# .husky/pre-push
npm test -- --passWithNoTests

Or run type checking + tests:

# .husky/pre-push
npm run typecheck && npm test

5. Full Setup Example

// package.json
{
  "scripts": {
    "prepare": "husky",
    "lint": "eslint src --ext .ts,.tsx",
    "lint:fix": "eslint src --ext .ts,.tsx --fix",
    "format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"",
    "format:check": "prettier --check \"src/**/*.{ts,tsx,css,json}\"",
    "typecheck": "tsc --noEmit",
    "test": "vitest run"
  },
  "lint-staged": {
    "src/**/*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "src/**/*.{css,json,md}": [
      "prettier --write"
    ]
  }
}
# .husky/pre-commit
npx lint-staged
# .husky/commit-msg
npx --no -- commitlint --edit $1
# .husky/pre-push
npm run typecheck

6. Hooks for Different Scenarios

Block commits with TODO/FIXME

# .husky/pre-commit
if git diff --cached | grep -E '^\+.*TODO|^\+.*FIXME' > /dev/null; then
  echo "Error: staged changes contain TODO or FIXME comments"
  echo "Resolve them or use git commit --no-verify to skip"
  exit 1
fi

npx lint-staged

Prevent direct commits to main

# .husky/pre-commit
branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
  echo "Direct commits to $branch are not allowed."
  echo "Create a feature branch and open a pull request."
  exit 1
fi

npx lint-staged

Check for large files

# .husky/pre-commit
MAX_SIZE=500  # KB

for file in $(git diff --cached --name-only); do
  if [ -f "$file" ]; then
    size=$(du -k "$file" | cut -f1)
    if [ $size -gt $MAX_SIZE ]; then
      echo "Error: $file is ${size}KB (max ${MAX_SIZE}KB)"
      echo "Add large files to .gitignore"
      exit 1
    fi
  fi
done

npx lint-staged

7. CI Integration

Husky hooks should NOT run in CI (CI has its own lint/test steps):

# .github/workflows/ci.yml
- name: Install dependencies
  run: npm ci
  env:
    HUSKY: 0  # disable Husky in CI

Or automatically skip in CI (Husky 9+ does this by default):

# Husky 9+ checks CI environment automatically
# The prepare script skips in CI environments
# (when CI=true env var is set, which GitHub Actions sets automatically)

8. Bypassing Hooks

When genuinely needed (emergency commits, fixing hook issues):

# Skip all hooks for one commit
git commit --no-verify -m "emergency: hotfix"

# Skip pre-push
git push --no-verify

# Disable Husky for the session
HUSKY=0 git commit -m "wip"

# Disable lint-staged for one commit
LINT_STAGED_SKIP=true git commit -m "wip"

Common Issues

Hook not executable

chmod +x .husky/pre-commit

Hooks not running after npm install

# Make sure prepare script exists
npm run prepare

ESLint errors blocking commits in CI

# Make sure HUSKY=0 is set in CI env

lint-staged running on deleted files

{
  "lint-staged": {
    "*.{ts,tsx}": "eslint --fix"  // lint-staged skips deleted files automatically
  }
}

Key Takeaways

HookPurposeSpeed
pre-commitLint + format staged filesFast (< 3s with lint-staged)
commit-msgValidate commit messageInstant
pre-pushType check + testsCan be slow (use wisely)
  • lint-staged makes pre-commit fast by only linting changed files
  • commitlint enforces commit message conventions for readable git history
  • Set HUSKY=0 in CI — CI has its own steps, don’t duplicate work
  • Keep hooks fast — developers bypass slow hooks with --no-verify
  • Hooks are version-controlled in .husky/ and installed via npm install

Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Automate code quality with Husky git hooks. Covers pre-commit linting with lint-staged, commit-msg validation with commi…

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers Husky, Git, ESLint, Prettier, Developer Tools, CI/CD.