Husky makes Git hooks easy: commands that run automatically before a commit or when you write its message. With it you can stop unformatted code, lint errors or messy commit messages from getting into the repository.
In this note we’ll set up, in this order:
- Prettier, to format code.
- ESLint, to catch errors.
- Husky, to run both before every commit.
- commitlint, to enforce Conventional Commits.
This guide uses Husky v9. If you see tutorials with
npx husky-initornpx husky add, they’re for older versions: those commands no longer work.
1. Prettier #
Install it:
npm install --save-dev --save-exact prettier Create a .prettierrc config file at the project root. This is the one I use:
{
"printWidth": 80,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"endOfLine": "auto"
} To format the whole project, or just check without changing files:
npx prettier --write .
npx prettier --check . Prettier ignores whatever is in .gitignore. If you need to ignore more, list it in a .prettierignore file.
2. ESLint #
ESLint has a wizard that creates the config for you:
npm init @eslint/config@latest Answer the questions (JavaScript or TypeScript, React or not, and so on) and it creates an eslint.config.mjs file. To check the project:
npx eslint . So ESLint and Prettier don’t fight over formatting, install eslint-config-prettier:
npm install --save-dev eslint-config-prettier And add it last in your eslint.config.mjs:
import eslintConfigPrettier from 'eslint-config-prettier';
export default [
// ...your current config
eslintConfigPrettier,
]; Finally, add two scripts to your package.json:
{
"scripts": {
"format": "prettier --write . && eslint --fix .",
"lint": "prettier --check . && eslint ."
}
} 3. Husky #
You need a Git repository that’s already initialized (git init). Install Husky and run its init command:
npm install --save-dev husky
npx husky init husky init does two things:
- Creates the
.husky/folder with apre-commithook. - Adds the
"prepare": "husky"script to yourpackage.json, so hooks install automatically when someone runsnpm install.
Now edit .husky/pre-commit so it runs your lint script. The file should contain just this line:
npm run lint From now on, if the code doesn’t pass Prettier or ESLint, the commit is cancelled.
On a large project, checking everything on every commit can be slow. With lint-staged you can check only the files you’re committing.
4. commitlint and Conventional Commits #
Install commitlint with the Conventional Commits config:
npm install --save-dev @commitlint/cli @commitlint/config-conventional Create commitlint.config.mjs at the root:
export default { extends: ['@commitlint/config-conventional'] }; And create the hook that checks every commit message, in .husky/commit-msg:
npx --no -- commitlint --edit $1 Try it #
Try a commit with any message:
git commit -m "changes" commitlint rejects it because it doesn’t follow the format. This one passes:
git commit -m "feat: add contact form" The most common types are feat (new feature), fix (bug fix), docs, refactor, test and chore.
Summary #
| Tool | What it does | Where it’s configured |
|---|---|---|
| Prettier | Formats code | .prettierrc |
| ESLint | Catches errors | eslint.config.mjs |
| Husky | Runs commands on every commit | .husky/pre-commit, .husky/commit-msg |
| commitlint | Checks the commit message | commitlint.config.mjs |
If you also want a clean history before a pull request, see how to squash commits in Git.