React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

eslintprettierintegrationformattinglinting

ESLint + Prettier Integration

Make ESLint and Prettier work together without conflicts by separating concerns: ESLint for code quality, Prettier for formatting.

Recipe

Quick-reference recipe card — copy-paste ready.

# Install the compatibility package
npm install --save-dev eslint-config-prettier
 
# That's it — no eslint-plugin-prettier needed
// package.json scripts (run them separately)
{
  "scripts": {
    "lint": "next lint",
    "lint:fix": "next lint --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "check-all": "npm run format:check && npm run lint"
  }
}

When to reach for this: Any project using both ESLint and Prettier (which is most React/Next.js projects).

Working Example

// eslint.config.mjs
import { FlatCompat } from "@eslint/eslintrc";
import { dirname } from "path";
import { fileURLToPath } from "url";
 
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
 
const compat = new FlatCompat({ baseDirectory: __dirname });
 
const eslintConfig = [
  ...compat.extends(
    "next/core-web-vitals",
    "next/typescript",
    "prettier", // Must be LAST — disables ESLint formatting rules
  ),
  {
    rules: {
      // Code quality rules (not formatting) — these won't conflict
      "@typescript-eslint/no-unused-vars": [
        "error",
        { argsIgnorePattern: "^_" },
      ],
      "react-hooks/exhaustive-deps": "warn",
      "import/order": [
        "warn",
        {
          groups: ["builtin", "external", "internal"],
          "newlines-between": "always",
        },
      ],
    },
  },
];
 
export default eslintConfig;
// .prettierrc
{
  "semi": true,
  "singleQuote": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80,
  "plugins": ["prettier-plugin-tailwindcss"]
}

What this demonstrates:

  • eslint-config-prettier (the "prettier" extend) disables all ESLint rules that conflict with Prettier
  • ESLint handles code quality only (unused vars, hook deps, import order)
  • Prettier handles formatting only (semicolons, quotes, indentation)
  • Separate npm scripts for each tool

Deep Dive

How It Works

  • ESLint has formatting rules (e.g., indent, semi, quotes) and code quality rules (e.g., no-unused-vars, react-hooks/exhaustive-deps)
  • Prettier also controls formatting, so the two tools fight over the same files
  • eslint-config-prettier turns off every ESLint rule that Prettier handles, eliminating conflicts
  • It must be the last item in extends so it overrides all previous configs

Variations

Why NOT to use eslint-plugin-prettier:

// ❌ This approach runs Prettier inside ESLint
// eslint-plugin-prettier — NOT recommended
{
  plugins: ["prettier"],
  rules: {
    "prettier/prettier": "error",
  },
}

Problems with this approach:

  • Every formatting issue shows as an ESLint error, producing noisy output
  • Linting becomes significantly slower because Prettier runs inside ESLint
  • Red squiggly lines on every formatting issue clutters the editor
  • Two tools doing the same job in the same process

Recommended approach: run them separately:

{
  "scripts": {
    "lint": "next lint",
    "lint:fix": "next lint --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "fix-all": "npm run lint:fix && npm run format"
  }
}

Pre-commit hook approach (best of both worlds):

// package.json
{
  "lint-staged": {
    "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{css,json,md}": ["prettier --write"]
  }
}

TypeScript Notes

// Both tools work on .ts and .tsx files seamlessly.
// ESLint catches type-aware issues:
const unused = "hello"; // @typescript-eslint/no-unused-vars ❌
 
// Prettier handles the formatting:
const obj = { a: 1, b: 2, c: 3 }; // Prettier controls spacing, trailing commas, etc.
 
// No overlap — each tool does its job.

Gotchas

Things that will bite you. Each gotcha includes what goes wrong, why it happens, and the fix.

  • Config order matters — If "prettier" is not the last item in extends, ESLint formatting rules from subsequent configs will re-enable and conflict. Fix: Always put "prettier" last in your extends array.

  • Checking for conflicts — You are not sure if a rule conflicts. Fix: Run npx eslint-config-prettier 'src/**/*.tsx' to list rules that conflict with Prettier. The CLI tool reports exactly which rules to turn off.

  • Different formatting in CI vs local — If developers do not have format-on-save enabled, CI catches formatting errors that they never saw. Fix: Use Husky + lint-staged to format automatically before each commit.

  • Import order is not formattingimport/order is a code quality rule that Prettier does not handle (Prettier does not reorder imports). Fix: Keep import/order in ESLint; it does not conflict with Prettier. Alternatively, use prettier-plugin-organize-imports.

Alternatives

Other ways to solve the same problem — and when each is the better choice.

AlternativeUse WhenDon't Use When
BiomeYou want linting + formatting in one tool with zero conflictsYou need the ESLint plugin ecosystem
Prettier only (no ESLint)Very small project with no code quality concernsYou want to catch bugs, unused vars, or hook violations
ESLint only (with formatting rules)You refuse to add another toolYou want consistent, opinionated formatting

FAQs

What does eslint-config-prettier actually do?
  • It turns off every ESLint rule that conflicts with Prettier.
  • It does not add any rules of its own.
  • It must be the last item in extends so it overrides all previous configs.
Why is eslint-plugin-prettier not recommended?
  • It runs Prettier inside ESLint, making linting significantly slower.
  • Every formatting issue appears as an ESLint error, producing noisy output.
  • Red squiggly lines on every formatting difference clutters the editor.
  • Running the tools separately is cleaner and faster.
Gotcha: What happens if "prettier" is not the last item in extends?
  • ESLint formatting rules from configs listed after "prettier" will re-enable.
  • This causes conflicts where ESLint and Prettier fight over the same formatting.
  • Always put "prettier" last in your extends array.
How do I check which ESLint rules conflict with Prettier?
npx eslint-config-prettier 'src/**/*.tsx'

This CLI tool reports exactly which active rules conflict with Prettier.

Does import/order conflict with Prettier?
  • No. Prettier does not reorder imports.
  • import/order is a code quality rule, not a formatting rule.
  • Keep it in ESLint; it works alongside Prettier without conflicts.
What is the recommended way to run both tools?
{
  "scripts": {
    "lint": "next lint",
    "lint:fix": "next lint --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "fix-all": "npm run lint:fix && npm run format"
  }
}

Run them as separate commands, not as a single combined tool.

How do I set up pre-commit hooks to run both ESLint and Prettier?
{
  "lint-staged": {
    "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{css,json,md}": ["prettier --write"]
  }
}

ESLint fixes code quality issues first, then Prettier formats.

Gotcha: Why do I see different formatting in CI versus locally?
  • Developers without format-on-save enabled see formatting errors only in CI.
  • Use Husky + lint-staged to format automatically before each commit.
  • This ensures committed code is always formatted, regardless of editor settings.
Does eslint-config-prettier affect TypeScript-specific rules?
  • It only disables formatting-related rules (indentation, spacing, quotes).
  • TypeScript code quality rules like @typescript-eslint/no-unused-vars are not affected.
  • Type-aware rules remain fully active.
Can I use Prettier for formatting and ESLint for TypeScript type-aware linting?
  • Yes. This is the recommended setup.
  • ESLint handles code quality (unused vars, hook deps, type-aware checks).
  • Prettier handles formatting (semicolons, quotes, indentation).
  • eslint-config-prettier ensures no overlap between the two.
What does the check-all script do?
npm run format:check && npm run lint
  • format:check verifies files are formatted without modifying them.
  • lint runs ESLint for code quality.
  • Use this in CI to validate without auto-fixing.