Incentive ?

Traditionally, I use Git Hook to verify code’s format and linting issues; For instance, I create the .git/hooks/pre-commit in your project, you can run npm run format:check script (which contains Prettier/ESLint related checking), and only allow a user to commit the exit code of the script is 0 (that is if the script succeed):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# [.git/hooks/pre-commit]
#!/bin/sh

# Check if the npm is installed and available
if command -v npm >/dev/null 2>&1; then
  # Run the format:check command and store the exit status in $?
  npm run format:check
  # This test checks if the exit code is not zero, which means the formatting check failed.
  if [ $? -ne 0 ]; then
    # stop and signal failure
    echo -e "\n\033[31m Commit aborted: Formatting check failed. \033[0m\n" >&2
    exit 1
  fi
else
  # stop and signal failure
  echo -e "\n\033[31m Pre-commit: npm not found; skipping format check. \033[0m\n" >&2
  exit 1
fi
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// [package.json]
{
    "type": "module",
    "scripts": {
        "format:write": "prettier --write .",
    },
    "devDependencies": {
        "prettier": "^3.7.4",
    }
}

The issue with this is that:

  1. all the git hook is stored locally inside the .git directory which is automatically ignored by git; Hence you cannot share it with your team and have them to use the same format linting check before committing.
  2. when running the format:check command, it checks all the files in your directory, which can take very long; sometimes you may want to only check the stages files that you are about to commit.

To solve this issue we can use Husky + Lint-Staged: Husky enables to to write git hook and share it with people, lint-staged allow you to run formatter linter check (and write) on the staged file only.


Step-by-Step ?

Step-1: install husky

1
2
3
npm install --save-dev husky
npx husky init 
rm .husky/pre-commit

(*the init command will generate the .husky directory with a pre-populated example hook running npm test, of which you can safely delete via rm .husky/pre-commit)

Step-2: install lint-staged

Install lint-staged package as a development dependency

1
npm install --save-dev lint-staged

Step-3: configure lint-staged

Populate the following lint-staged configuration into your package.json (there are other ways to populate your lint-stage configuration, for instance lint-staged.config.js, see: https://github.com/lint-staged/lint-staged/tree/main#configuration)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
    "devDependencies": {
    	"husky":        "^9.1.7",
    	"lint-staged":  "^17.1.1",
        "cspell":       "^6.18.1",
        "eslint":       "^10.7.0",
        "prettier":     "^3.4.2",
        // ...
    },
	// ...
	"lint-staged": {
        "*.{js,ts,json,html,xml,css,scss,sass,md}": ["cspell --no-must-find-files"],
        "src/**/*.{js,ts}":                         ["eslint --max-warnings 0 --fix"],
        "*.{ts,js}":                                ["prettier --write"],
        "*":                                        ["prettier --write --ignore-unknown"]
    }
}

Note that, you will have to install the dependencies you’ve used in lint-staged command for instance, npm i prettier / npm i cspell / npm i prettier / npm i eslint. After that you can run lint-staged command using npx:

2026-07-22T183400

Step-4: setup git hook (pre-commit)

Then let’s use husky to trigger lint-staged using git hook whenever a git commit command is being run:

1
2
3
# [.huskey/pre-commit]
#!/usr/bin/env sh
npx lint-staged

Step-5: trial perform commit

Finally let’s trial making a commit, and check if the git hook gets trigged by husky to use lint-staged to run formatter/linter for the staged files:

2026-07-22T191936

(*Note that, by default, lint-staged creates a backup before running formatting or linting tasks. If a task fails, it can restore the state that existed before execution.)


Reference