Chapter 8.5 — Husky + lint-staged☕ 18 min read

Husky + lint-staged: Shared Hooks & Fast Linting

Husky = hooks ko repo mein store karo. lint-staged = sirf staged files lint karo. Fast, shared, effective.

01The Problem: Hooks Don't Travel

In Chapter 8.1, we learned that .git/hooks/ is NOT tracked by Git.

Every developer must manually set up hooks after cloning. They WILL forget.

Without shared hooks: one developer has pre-commit linting, another doesn't. Inconsistent quality.

The solution: store hooks IN the repository (tracked by Git), and install them to .git/hooks/ automatically.

Husky does exactly this. It's the industry standard for sharing Git hooks in JavaScript/TypeScript projects.

Before Husky, teams used manual scripts or lefthook. Husky won because of simplicity and npm integration.

For non-JS projects: alternatives include lefthook (language-agnostic), pre-commit framework (Python).

# The problem without Husky:
# Developer A creates a pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
npm run lint
EOF

# This hook ONLY exists on Developer A's machine
# .git/ is not tracked by Git

# Developer B clones the repo
git clone https://github.com/team/project.git
cd project

# Developer B has NO pre-commit hook
ls .git/hooks/pre-commit
# No such file or directory

# Developer B commits with lint errors
echo "const x=1" > messy.js
git add messy.js
git commit -m "add messy code"
# ✅ Commit succeeds — NO linting!
# Bad code enters the repository!

# With Husky: Developer B runs npm install
# Husky automatically sets up .git/hooks/ from repo config
# Developer B has the SAME hooks as Developer A
02Husky Setup: From Zero to Automated

Husky stores hook definitions in your project directory (tracked by Git).

On npm install, Husky's prepare script runs and installs hooks to .git/hooks/.

Setup steps: install husky → initialize → add hooks → commit .husky/ directory.

Modern Husky (v9+): npx husky init creates .husky/ directory with a sample pre-commit hook.

The prepare script in package.json ensures hooks are installed on every npm install.

Hooks in .husky/ are just shell scripts — same as .git/hooks/ but tracked by Git.

Every team member who runs npm install gets the same hooks automatically.

# Step 1: Install Husky
npm install --save-dev husky

# Step 2: Initialize Husky (creates .husky/ directory)
npx husky init
# Creates:
# .husky/pre-commit (sample hook)
# Adds "prepare": "husky" to package.json

# Step 3: Check what was created
cat package.json | grep prepare
# "prepare": "husky"

ls .husky/
# pre-commit

cat .husky/pre-commit
# npm test

# Step 4: Customize the pre-commit hook
echo "npx lint-staged" > .husky/pre-commit

# Step 5: Commit .husky/ to the repo
git add .husky/ package.json package-lock.json
git commit -m "chore: add Husky hooks"

# Now every developer who clones and runs npm install
# will automatically get the pre-commit hook!
03lint-staged: Fast Linting on Staged Files Only

The #1 problem with pre-commit linting: linting the ENTIRE project is slow.

lint-staged solves this: it runs linters ONLY on files that are staged for commit.

If you change 2 files, lint-staged runs ESLint on only those 2 files — instant.

lint-staged also AUTO-FIXES what it can (formatting, simple lint errors) and re-stages the fixed files.

Configuration goes in package.json or .lintstagedrc.json.

Common config: { "*.js": ["eslint --fix", "prettier --write"], "*.css": ["prettier --write"] }.

The pattern: Husky triggers pre-commit → lint-staged runs → only staged files are checked → fast!

💡 Pro Tip: lint-staged is the secret to fast pre-commit hooks. Without it, linting all files takes 10-30 seconds. With lint-staged, linting only staged files takes 1-3 seconds. This is the difference between hooks that developers use and hooks that developers bypass with --no-verify.
04The Complete Husky + lint-staged Setup

This is the industry standard setup for JavaScript/TypeScript projects.

Step 1: Install both packages.

Step 2: Initialize Husky and create pre-commit hook that runs lint-staged.

Step 3: Configure lint-staged in package.json.

Step 4: Add commit-msg hook for commitlint (optional).

Step 5: Add pre-push hook for running tests (optional).

Step 6: Commit everything — team members get hooks automatically on npm install.

# Complete setup — copy this for any new project

# Step 1: Install
npm install --save-dev husky lint-staged prettier eslint

# Step 2: Initialize Husky
npx husky init

# Step 3: Create pre-commit hook with lint-staged
echo "npx lint-staged" > .husky/pre-commit

# Step 4: Configure lint-staged in package.json
# Add this to your package.json:
# "lint-staged": {
#   "*.{js,ts}": ["eslint --fix", "prettier --write"],
#   "*.{json,md,css,html}": ["prettier --write"]
# }

# Step 5: Optional — add commit-msg hook for commitlint
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg

# Step 6: Optional — add pre-push hook for tests
echo "npm test" > .husky/pre-push

# Step 7: Commit everything
git add .husky/ package.json package-lock.json
git commit -m "chore: add Husky + lint-staged"

# Now your team workflow:
# 1. Developer clones repo
git clone https://github.com/team/project.git
cd project

# 2. npm install sets up hooks automatically
npm install
# husky - Git hooks installed

# 3. Developer commits — hooks run automatically!
echo "const x = 1" > app.js
git add app.js
git commit -m "feat: add app"
# ✔ Pre-commit hook runs lint-staged
# ✔ ESLint + Prettier run on app.js only
# ✔ Commit proceeds with clean code
05Advanced Husky Configurations

Conditional hooks: run different checks based on file type or branch.

Skipping hooks in CI: set HUSKY=0 environment variable to disable hooks in CI environments.

Monorepo support: Husky works in monorepos — install at root, hooks apply to all packages.

Custom hook directory: Husky v9 uses .husky/ by default. You can customize the path.

Troubleshooting: if hooks don't run, check that prepare script exists in package.json.

Non-JS projects: Husky works with any language — hooks are just shell scripts. Use Python, Go, Rust commands.

Husky is the most popular Git hooks manager for JS projects with 30M+ weekly npm downloads. But it's not the only option. For Python projects, use the pre-commit framework. For language-agnostic projects, try lefthook. The concept is the same: store hooks in the repo, install them automatically.

Lo kar liya — Key Points:

  • ✅ Husky stores Git hooks in your project directory (.husky/) — tracked by Git, shared with team
  • ✅ On npm install, Husky automatically installs hooks from .husky/ to .git/hooks/
  • ✅ lint-staged runs linters ONLY on staged files — 10x faster than linting the entire project
  • ✅ The combination: Husky triggers pre-commit → lint-staged runs → only staged files are checked
  • ✅ lint-staged auto-fixes and re-stages formatted files — commit contains clean code automatically
  • ✅ Every team member gets the same hooks after running npm install — no manual setup needed
  • ✅ Set HUSKY=0 in CI to disable hooks — CI doesn't need local hooks
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase