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.
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:
npm init -yThe -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:
{}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:
npm install --save-dev prettierAdd a format script to package.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.
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:
npm install --save-dev eslint@9.9.1 @eslint/js@9.9.1 globals@15.9.0 eslint-config-prettier@9.1.0Create eslint.config.mjs at the root (.mjs because ESLint 9 uses ES modules for its config):
// @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:
{
"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:
npm run lint -- --fixThe -- 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
- Prettier handles formatting, ESLint handles bugs. Formatting means whitespace and style. Bugs means misused APIs, undefined globals, debugger statements left in production.
- Empty
.prettierrcis intentional. Defaults are good. The goal is to stop thinking about formatting, not to configure a new set of opinions. eslint-config-prettiermust be last. It disables ESLint formatting rules so the two tools do not conflict. Order matters in flat ESLint config.- Declare your globals.
globals.browserandglobals.nodestop ESLint flaggingdocument,window,process, and similar as undefined. - The
--separator.npm run lint -- --fixpasses--fixto ESLint. Without--, npm intercepts the flag and ESLint never sees it.
Further Reading and Watching
- Prettier's Design Goals -- the Prettier team's own explanation of why an opinionated formatter is better than a configurable one
- ESLint Getting Started -- official ESLint v9 setup guide, covers flat config format in depth
Keep reading