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-prettier turns off all ESLint rules that would conflict with Prettier.
  • eslint-plugin-prettier is 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,
    },
  },
];

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