Integrating Linters, Formatters, and Git Hooks
What we want is that developers write code and the linting and formatting happen automatically: on save, on commit, and in CI.
Configuring ESLint and Prettier Together
The problem is that ESLint and Prettier can conflict. ESLint has some style-related rules (like indentation and quote style) that overlap with what Prettier does. If the two tools enforce different style rules, they keep undoing each other’s fixes.
The package you need is:
npm install --save-dev eslint-config-prettier
eslint-config-prettierturns off all ESLint rules that would conflict with Prettier.eslint-plugin-prettieris optional. It runs Prettier as an ESLint rule, but many teams skip it and run Prettier directly for better performance and simpler output.
Here is the common flat-config setup:
// eslint.config.js
import js from "@eslint/js";
import prettierConfig from "eslint-config-prettier";
export default [
js.configs.recommended,
prettierConfig,
{
rules: {
"no-unused-vars": "warn",
"prefer-const": "error",
},
},
];
Use Prettier separately (prettier --write) in scripts, your editor, or lint-staged.
Setting Up Editor Integration
Install these VS Code extensions: ESLint and Prettier - Code formatter. Then add this to your .vscode/settings.json:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
With these settings, every time you save a file, ESLint lints it and Prettier formats it.
Enforcing Quality with Pre-commit Hooks
Editor integration helps, but it relies on every developer installing the extensions and configuring the settings described above. Pre-commit hooks are a backup. They run linting and formatting on staged files before each commit, and if the checks fail, the commit is rejected.
Set up Husky (for Git hooks) and lint-staged (for running commands on staged files only):
npm install --save-dev husky lint-staged
npx husky init
Configure lint-staged in your package.json:
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,css}": ["prettier --write"]
}
}
Then edit the Husky pre-commit hook:
# .husky/pre-commit
npx lint-staged
Now every commit lints and formats only the files being committed. The rest of the repository is not checked, so the commit does not take long.
Adding TypeScript Support
For TypeScript projects, install the TypeScript ESLint meta-package:
npm install --save-dev typescript-eslint
// eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.ts", "**/*.tsx"],
languageOptions: {
parser: tseslint.parser,
},
},
];
Using Popular Shared Configurations
Instead of writing every rule yourself, you can extend a shared config. These are the ones teams use most often:
| Config | Style |
|---|---|
eslint-config-airbnb-base |
Strict, opinionated (very popular) |
eslint-config-standard |
No semicolons, minimal |
eslint-config-google |
Google’s internal style |
Many shared configs were built for the older .eslintrc format and may need compatibility helpers in flat config. Start with a simple config of your own, and add a shared config when you need it.
Summary of the Toolchain
| Tool | Purpose |
|---|---|
| ESLint | Find bugs and enforce best practices |
| Prettier | Format code for consistent style |
| eslint-config-prettier | Prevent rule conflicts between the two |
| Husky | Run scripts on Git hooks (like pre-commit) |
| lint-staged | Run linters only on staged files |