Skip to content
Back to Build Tutorials

Lint Setup

Configure ESLint + Prettier + TypeScript strict so style and type errors are caught pre-commit.

Why

Linters are the cheapest code review you will ever get — they run in 2 seconds and catch the 80% of issues that don't require human judgment. Without them, every PR review wastes 15 min on 'use const here' and 'this hook is unconditional', leaving no time for the architecture feedback that actually matters. TypeScript strict mode is non-negotiable: it turns runtime crashes into compile errors for null access, implicit any, and missing return types. The combination of the three, enforced by a pre-commit hook, means the code that reaches review is already at a baseline quality, so review focuses on intent rather than syntax.

When

During project-init, before the first feature commit. Takes 30 min. Skip only for a throwaway script under 50 lines.

How

1. Install the trio: eslint + prettier + typescript-eslint
   - pnpm add -D eslint prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser
   - Use eslint --init to pick the config style, then extend it

2. Enable TypeScript strict in tsconfig.json
   - "strict": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true
   - Fix the resulting errors — do NOT downgrade to silence them

3. Add eslint-config-prettier to disable conflicting rules
   - extends: ["next/core-web-vitals", "prettier"]
   - This makes ESLint defer formatting to Prettier

4. Wire a pre-commit hook with lint-staged
   - pnpm add -D husky lint-staged
   - npx husky init; edit .husky/pre-commit to run "npx lint-staged"
   - lint-staged runs eslint --fix and prettier --write only on changed files

5. Add the lint script to package.json and CI
   - "lint": "eslint . --ext .ts,.tsx --max-warnings 0"
   - CI fails on any warning — warnings rot into ignored noise otherwise

Example

// .eslintrc.json
{
  "extends": ["next/core-web-vitals", "prettier"],
  "rules": {
    "@typescript-eslint/no-floating-promises": "error",
    "no-console": ["warn", { "allow": ["warn", "error"] }]
  },
  "ignorePatterns": ["dist/", ".next/"]
}

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

Common Problems

  • ⚠Enabling so many rules that lint takes 30s and devs start committing with --no-verify to skip the hook.
  • ⚠Setting --max-warnings 0 but then adding 50 eslint-disable comments instead of fixing the root cause.
  • ⚠Running prettier and eslint with conflicting rules, so they fight and reformat each other on every save.
  • ⚠Not including the lint step in CI, so the pre-commit hook is the only gate and it's bypassable with --no-verify.

Best Practices

  • ✓Use eslint-config-prettier (not -recommended) — it only disables, never adds rules, so there's zero conflict.
  • ✓Keep lint under 5s by using lint-staged in the hook and full-repo lint only in CI.
  • ✓Treat eslint-disable as a code smell requiring a review comment explaining why — never let it become routine.
  • ✓Turn on noUncheckedIndexedAccess last, after the rest is green — it generates the most errors but prevents the most runtime crashes.