Prettier And Eslint

Prettier kills formatting arguments forever and ESLint catches the bugs that slip through review -- understanding which tool does which job stops them from fighting each other.

May 15, 20264 min read1 / 2

Every codebase I have worked on has had at least one meeting about tabs versus spaces, or whether trailing commas belong on the last item of an array, or which quote character is canonical. Those meetings are a complete waste of engineering time. The answer is: pick anything, automate it, and never discuss it again.

Prettier is that automation. ESLint is something different.

Starting a Package

Before installing either tool, the project needs a package.json. Run this in the project root:

Bash
npm init -y

The -y flag skips the question wizard and generates a default file. You now have a place to record dependencies and scripts.

Prettier

Create a .prettierrc file at the root of the project:

JSON
{}

An empty object means "use all defaults." That is intentional. Prettier's defaults are sensible. The point is not to configure it -- the point is to stop thinking about formatting entirely and let the tool decide.

Install Prettier as a dev dependency:

Bash
npm install --save-dev prettier

Add a format script to package.json:

JSON
{ "scripts": { "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css}\"" } }

The quotes around the glob are important. Without them, your shell expands the pattern before Prettier sees it, and the results are unpredictable. Pass it as a literal string so Prettier does its own file discovery.

Run npm run format to format everything in src/. You will likely see "unchanged" on a clean project -- that just means Prettier agrees with how the files are already written.

The VS Code extension is worth installing alongside this. With "Format on Save" enabled, you stop thinking about formatting completely. The script exists for CI pipelines and for teammates who use different editors -- running npm run format in a GitHub action before merge makes formatting a non-issue for everyone.

ESLint

Prettier and ESLint solve different problems. Prettier handles whitespace and style -- things that are arbitrary, things where neither choice is wrong, things you do not want to argue about. ESLint handles semantics -- things that indicate a real problem or a dangerous pattern.

Prettier handles formatting while ESLint catches real bugs ExpandPrettier handles formatting while ESLint catches real bugs

A debugger statement shipped to production is a bug. An unused variable usually means a typo. A reference to window or document without declaring the browser environment causes false errors. ESLint catches all of these.

Install at specific versions to avoid breaking changes mid-course:

Bash
npm install --save-dev eslint@9.9.1 @eslint/js@9.9.1 globals@15.9.0 eslint-config-prettier@9.1.0

Create eslint.config.mjs at the root (.mjs because ESLint 9 uses ES modules for its config):

JavaScript
// @type {import("eslint").Linter.Config[]} import js from "@eslint/js"; import globals from "globals"; import prettier from "eslint-config-prettier"; export default [ js.configs.recommended, { files: ["**/*.{js,jsx}"], languageOptions: { globals: { ...globals.browser, ...globals.node, }, parserOptions: { ecmaFeatures: { jsx: true, }, }, }, }, prettier, ];

A few things in this config are worth understanding:

js.configs.recommended is a small, non-controversial set of rules. It catches real mistakes without enforcing opinions. The .all config exists but enables hundreds of rules you almost certainly do not want -- stick with .recommended.

globals.browser and globals.node tell ESLint which global variables are valid. Without declaring browser globals, ESLint flags every reference to document or window as an undefined variable. Without node globals, anything using process or __dirname fails. Both are spread together here because the project may contain both browser and Node code.

prettier must be last. This configuration only disables formatting rules in ESLint -- rules that overlap with what Prettier already handles. It does not add anything. Placing it last ensures it overrides any formatting rules that earlier configs might enable. If it is not last, ESLint and Prettier will disagree and you will spend time chasing phantom formatting errors.

Add a lint script:

JSON
{ "scripts": { "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css}\"", "lint": "eslint" } }

Run npm run lint. You may see errors on the existing code -- that is the point. To auto-fix the ones ESLint can handle:

Bash
npm run lint -- --fix

The -- passes subsequent flags to ESLint rather than to npm itself.

In the next post Vite replaces the CDN script tags and the project gets a proper dev server, a build pipeline, and npm-installed React.

The Essentials

  1. Prettier handles formatting, ESLint handles bugs. Formatting means whitespace and style. Bugs means misused APIs, undefined globals, debugger statements left in production.
  2. Empty .prettierrc is intentional. Defaults are good. The goal is to stop thinking about formatting, not to configure a new set of opinions.
  3. eslint-config-prettier must be last. It disables ESLint formatting rules so the two tools do not conflict. Order matters in flat ESLint config.
  4. Declare your globals. globals.browser and globals.node stop ESLint flagging document, window, process, and similar as undefined.
  5. The -- separator. npm run lint -- --fix passes --fix to ESLint. Without --, npm intercepts the flag and ESLint never sees it.

Further Reading and Watching