React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

best-practicessummarylinting-formatting

Linting & Formatting Best Practices

A condensed summary of the 25 most important best practices drawn from every page in this section.

  1. Use ESLint 9 Flat Config: Adopt eslint.config.mjs and delete every leftover .eslintrc.* file, because ESLint falls back to the legacy format if both exist and your flat config is silently ignored.
  2. Bridge Presets With FlatCompat: Next.js presets still ship legacy extends-style configs, so wrap them with FlatCompat({ baseDirectory: __dirname }) and recreate __dirname via fileURLToPath(import.meta.url) in ESM.
  3. Isolate Global Ignores: In flat config, { ignores: [...] } only acts as a global ignore when it is the sole key in its object; mixing it with rules turns it into a per-file filter.
  4. Start From next/core-web-vitals: It bundles eslint-plugin-react, react-hooks, next, import, and jsx-a11y, so you rarely need to install those plugins separately and can layer only what is missing.
  5. Pick Severity Intentionally: Use "error" for rules you want to enforce (fail CI, block builds) and reserve "warn" for rules you are migrating toward, since warnings pass CI and silently accumulate.
  6. Prefer the TypeScript no-unused-vars: Turn the base no-unused-vars off and enable @typescript-eslint/no-unused-vars with argsIgnorePattern: "^_" so interface/type-alias/enum syntax is understood and conflicts disappear.
  7. Enforce consistent-type-imports: Require import type { … } for type-only imports so bundlers can fully erase types and your type graph stays clearly separated from your value graph.
  8. Never Disable exhaustive-deps Globally: Keep react-hooks/exhaustive-deps on (usually as "warn") and suppress it per-line with // eslint-disable-next-line only for provably stable references like dispatch or refs.
  9. Order Imports With Group Separators: Configure import/order with grouped categories (builtin, external, internal, parent/sibling, index, type), "newlines-between": "always", and alphabetization; run eslint --fix once and add any missing blank lines by hand.
  10. Don't Re-register Bundled Plugins: Installing a plugin that next/core-web-vitals already includes (react, react-hooks, next, import, jsx-a11y) causes duplicate registration and unexpected rule behavior, so check the preset first.
  11. Scope Test-Only Plugins With files: Gate eslint-plugin-testing-library behind files: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"]; applied globally it produces false positives on non-test code.
  12. Profile Slow Rules: Run TIMING=1 npx eslint . when lint times creep up — five or more active plugins can meaningfully slow linting, and type-aware @typescript-eslint rules are 2-5x slower because they require full type info.
  13. Put eslint-config-prettier Last: The "prettier" entry must be the final item in your extends array, because it only turns off formatting rules and any config listed after it will re-enable conflicts.
  14. Don't Use eslint-plugin-prettier: Running Prettier inside ESLint is slow and floods the editor with red squiggles for pure formatting differences — run ESLint and Prettier as separate commands instead.
  15. Always Ship a .prettierignore: Without it Prettier walks .next/, node_modules/, lock files, and generated code; an explicit ignore file keeps runs fast and prevents unintended changes.
  16. List Tailwind Plugin Last: prettier-plugin-tailwindcss must sit at the end of the Prettier plugins array or it will conflict with other Prettier plugins and class sorting breaks.
  17. Pin endOfLine to lf: Set "endOfLine": "lf" in .prettierrc and configure git config --global core.autocrlf input so Windows contributors do not ship CRLF files that fail prettier --check on Linux CI.
  18. Commit .vscode Team Settings: Check in .vscode/settings.json and .vscode/extensions.json (not launch.json or *.code-workspace) so every developer inherits the same format-on-save, ESLint, and recommended-extensions behavior.
  19. Set defaultFormatter Per Language: Declare editor.defaultFormatter both globally and per language ([typescript], [typescriptreact], [json]) to avoid VS Code picking a random formatter when multiple are installed.
  20. Use the Workspace TypeScript: Point typescript.tsdk at node_modules/typescript/lib so editor type-checking matches the version tsc uses in CI, eliminating "works on my machine" type drift.
  21. Enable strict Plus Extra Flags: Turn on "strict": true for the eight bundled checks and add noUncheckedIndexedAccess, noImplicitReturns, noFallthroughCasesInSwitch, and verbatimModuleSyntax because they are not included in strict.
  22. Adopt Strict Incrementally: On a legacy codebase, keep "strict": false and enable strictNullChecks first, then noImplicitAny, using // @ts-expect-error (not @ts-ignore) so suppressions auto-fail once the underlying issue is fixed.
  23. Run tsc --noEmit in CI: TypeScript catches cross-file type breaks that ESLint cannot (for example, removing a union member from a shared type); add "type-check": "tsc --noEmit" and rely on incremental: true plus a cached .tsbuildinfo for speed.
  24. Install Husky via prepare: Put "prepare": "husky" in package.json so pre-commit hooks are installed automatically after npm install, and keep hooks fast by running only lint-staged (with eslint --fix --no-warn-ignored) — never full tsc.
  25. Make CI the Safety Net: Run lint, prettier --check, and tsc --noEmit in GitHub Actions using npm ci for deterministic installs, a matching pinned Node version, a concurrency group with cancel-in-progress: true, and branch-protection rules that require the job to pass before merge.