ESLint vs Prettier Config Wars — One Setup That Stops Fights
The PR has 40 files changed. Three of them are real logic. The rest are semicolons, quote style, and import order flipping because Alice’s VS Code uses ESLint fix-on-save with an old .eslintrc and Bob’s uses Prettier with a different printWidth. The Slack thread is titled “tabs vs spaces” for the thirteenth time. You do not need a better opinion. You need one pipeline where ESLint does not fight Prettier.
Split responsibilities cleanly
| Tool | Owns | Does not own |
|---|---|---|
| Prettier | Spaces, quotes, wrapping, trailing commas | Unused vars, hooks rules, import legality |
| ESLint | Correctness, bugs, team policy | Re-indenting what Prettier already formatted |
When both format the same syntax differently, you get ping-pong commits. The fix is eslint-config-prettier: it disables ESLint rules that conflict with Prettier so only one formatter runs.
The boring setup that works in 2026
npm i -D eslint prettier eslint-config-prettier eslint-plugin-prettier
# or: prettier as CLI only, and skip eslint-plugin-prettier if you want faster lint
eslint.config.js (flat config sketch):
import prettier from "eslint-config-prettier";
import js from "@eslint/js";
export default [
js.configs.recommended,
// ... typescript / react configs ...
prettier, // LAST — turns off conflicting stylistic rules
];
.prettierrc:
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100
}
Run format and lint as separate scripts:
{
"scripts": {
"format": "prettier --write .",
"lint": "eslint .",
"check": "prettier --check . && eslint ."
}
}
eslint-plugin-prettier (run Prettier as an ESLint rule) is optional. It unifies the error channel but slows lint. Many teams prefer Prettier in CI via --check and ESLint for logic only.
Import order: pick one owner
Import sorting is the last battleground. Either:
- eslint-plugin-simple-import-sort (or
import/order) and tell Prettier to leave imports alone, or - @trivago/prettier-plugin-sort-imports / prettier-plugin-organize-imports and turn off ESLint import ordering.
Two sorters = eternal war. Document the choice in CONTRIBUTING.md in one sentence.
Editor and CI must match
- Commit
.vscode/settings.json(or Cursor equivalents) witheditor.defaultFormatterfor Prettier andsource.fixAllon save for ESLint code actions only, not a second formatter. - Fail CI on
prettier --checkandeslint— not on “looks fine on my machine.” - Same Node/Prettier major version via
packageManager/ Volta /.nvmrcso CLI and editor plugin do not diverge.
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
Rules that still belong in ESLint
Keep no-unused-vars, react-hooks/exhaustive-deps, @typescript-eslint/no-floating-promises, and security plugins. Delete or disable indent, semi, quotes, comma-dangle — Prettier owns those.
Ending an existing war
- Add
eslint-config-prettierlast in the config chain. - One PR that only runs Prettier across the repo (agree on a merge freeze window).
- Delete conflicting local settings from wiki folklore.
- Ban “format-only” nit comments in review — the bot already owns that.
Monorepo note
Share one root Prettier config and one ESLint flat config across packages. If a package truly needs a different printWidth (generated GraphQL, huge markdown tables), use Prettier overrides — do not invent a second formatting religion. Run prettier --check and eslint in the same CI job so contributors see both failures together instead of ping-ponging across separate green checks.
Config wars are process failures. Give Prettier the aesthetics, ESLint the bugs, one sort plugin, and identical CI. Then the next PR diff can be about the feature again.