Web development

ESLint and Prettier without conflicts: flat config, one command

ESLint 10 flat config, Prettier as the only owner of formatting, and eslint-config-prettier last in the list: no rules cancelling each other.

Key points

  • Prettier formats, ESLint checks correctness: `eslint-config-prettier` goes last and turns off the rules that overlap.
  • Locally run `prettier . --write`, in CI `prettier . --check`: `--write` in CI produces a green job that fixed nothing in the repository.
  • A plugin still on the `eslintrc` format needs `FlatCompat`: check it before planning the move to ESLint 10.

Short answer: leave formatting to Prettier and correctness to ESLint, close the ESLint list with eslint-config-prettier, and run prettier . --check && eslint . from one script. The two tools then stop rewriting the same file.

Who does what

  • Prettier decides spaces, commas and line breaks: one configuration for the whole repository.
  • ESLint decides what counts as a code error: unused variables, missing imports, framework rules.
  • eslint-config-prettier turns off every ESLint style rule that would fight Prettier.

Without that third step the two tools cancel each other: eslint --fix restores an indent that prettier --write changes again. The result is a diff on every save and a team that switches the checks off.

The minimal setup, in flat config

import js from "@eslint/js";
import tseslint from "typescript-eslint";
import prettier from "eslint-config-prettier";

export default tseslint.config(
  js.configs.recommended,
  ...tseslint.configs.recommended,
  prettier,
  { ignores: ["dist/", ".next/"] },
);

Order matters: prettier goes last because it disables rules the earlier presets enabled. Projects with plugins still on the eslintrc format need FlatCompat, documented in the migration guide. ESLint, Prettier and eslint-config-prettier are all MIT licensed.

One command before the commit

npm i -D eslint@10 prettier@3 typescript-eslint eslint-config-prettier
npx eslint .
npx prettier . --check

Locally format, in CI check: --write changes files, --check exits with code 1 when something is unformatted. Putting --write in CI produces a green job that fixed nothing in the repository.

The errors that remain

Two cases come back often: an ESLint rule Prettier does not know about, and a file ignored by one tool but not the other. Keep exclusions in one place, .prettierignore, and align that list with ignores in the flat config.

A named limitation

Not every published plugin ships a flat config yet: a package still on the eslintrc format needs FlatCompat and deserves a check before you plan the migration. A plugin that lags behind can block adoption for longer than the guide suggests.

Where to continue

In the catalogue start from every component, the multi-step form and the leads table. For the rest read Vitest in a React project, the common build errors and the comparison Next.js or Vite.

Frequently asked questions

Do ESLint and Prettier do the same job?

No. ESLint looks for code errors and risky patterns, Prettier rewrites shape only. Overlapping them is the most common cause of conflicts, and eslint-config-prettier exists to remove the overlap.

In what order do the presets go in the flat config?

Language and framework presets first, eslint-config-prettier last. The last preset that enables a rule wins, so Prettier has to close the list.

eslint --fix or prettier --write?

It depends on what you want fixed: prettier --write handles shape, eslint --fix handles fixable violations. Run Prettier first and ESLint second, never alternating on the same file.

Why does CI fail when everything is formatted locally?

Almost always a different Prettier version, or a file ignored only on your machine. Pin the version in package.json and make sure .prettierignore is in the repository.

Can I keep Prettier only and drop ESLint?

You can, but you lose checks Prettier does not run: unused variables, broken imports, React or TypeScript specific rules. The pair costs little and covers two different problems.