Concept
ESLint, code quality and bug prevention
ESLint is a static analysis tool that finds problems in your JavaScript/TypeScript. It reads your code without running it and reports violations of configurable rules.
What ESLint catches:
- Actual bugs: unused variables, unreachable code, missing
await,==vs=== - Code quality: complexity, naming conventions, import order
- Security:
eval(),dangerouslySetInnerHTMLmisuse - React-specific: missing dependency arrays in hooks, key prop violations
What ESLint doesn't catch: formatting (indentation, quotes, semicolons), that's Prettier's job.
ESLint flat config (modern)
ESLint v9 introduced a new "flat config" format. This is what new projects should use:
// eslint.config.mjs
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
plugins: {
react: reactPlugin,
'react-hooks': reactHooks,
},
rules: {
'react-hooks/rules-of-hooks': 'error',
ESLint rule severities
'off' or 0, disabled
'warn' or 1, violation shown as warning (CI can still pass)
'error' or 2, violation shown as error (CI fails)Use 'error' for bugs and security issues. Use 'warn' for style suggestions you want to migrate toward. Don't leave 'warn' rules forever, they accumulate and become noise.
Prettier, opinionated formatting
Prettier reformats your code to a consistent style. The key insight: Prettier removes all formatting decisions. There's no "tabs vs spaces" debate, Prettier decides and enforces.
Prettier handles: indentation, quotes (single/double), semicolons, trailing commas, line length, bracket spacing, arrow function parens.
Prettier does NOT handle: code logic, variable names, import order, that's ESLint.
// .prettierrc.json
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "always"
}npx prettier --write src/**/*.ts # format all TypeScript files
npx prettier --check src/**/*.ts # check without writing (CI mode)Prettier + ESLint integration
They overlap on some rules (quotes, semicolons). The solution: disable ESLint's formatting rules and let Prettier handle them.
npm install -D eslint-config-prettier// eslint.config.mjs, add prettier at the end (overrides conflicting rules)
import prettier from 'eslint-config-prettier';
export default [
...tseslint.configs.recommended,
// ... your rules
prettier, // must be last, disables ESLint formatting rules
];Pre-commit hooks with lint-staged + husky
The best time to catch lint/format errors is before they're committed, not in CI 5 minutes later.
husky installs Git hooks. lint-staged runs tools only on staged files (fast, not the whole codebase).
npm install -D husky lint-staged
npx husky init// package.json
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,css}": ["prettier --write"]
}
}# .husky/pre-commit
npx lint-stagedNow every git commit runs ESLint + Prettier on staged files. Violations block the commit.
CI enforcement
Pre-commit hooks can be bypassed with git commit --no-verify. CI is the final gate.
# .github/workflows/lint.yml
- run: npx eslint . --max-warnings 0
- run: npx prettier --check .--max-warnings 0 makes warnings fail the build. This forces warnings to be either fixed or explicitly promoted to errors.
Common Mistakes
1. Running ESLint + Prettier on the whole project in pre-commit hooks
Slow on large projects. lint-staged fixes this, it runs only on staged files.
2. Conflicting Prettier and ESLint rules without eslint-config-prettier
You get prettier/prettier: error on every formatted file. Always add eslint-config-prettier as the last config entry.
3. Ignoring lint warnings in CI
eslint . --max-warnings 100 lets 100 warnings accumulate. They become permanent noise. Use --max-warnings 0 or fix/convert them to errors.
4. Not adding .eslintignore / ignores for generated files
ESLint on node_modules, dist, or generated files is wasted CPU. Always ignore them:
// eslint.config.mjs
export default [
{ ignores: ['dist/**', 'node_modules/**', '*.generated.ts'] },
// ...
];5. Installing ESLint plugins without the corresponding config
Plugins only register rules; they don't enable them. You need both the plugin AND the config/recommended rules.
Best Practices
- Autofix on save in your editor: VS Code:
"editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }+ Prettier format on save. - Use
eslint --fixin lint-staged: automatically fixes fixable issues (avoids trivial "fix trailing comma" commits). - Start with
recommendedconfigs then add project-specific rules. Don't write rules from scratch. - Pin ESLint and Prettier versions, formatting changes between Prettier minor versions can cause mass reformatting commits if different team members run different versions.
