Skip to content

ESLint how-to

Lint a Next.js app with ESLint 9 flat config.

Install

bash
pnpm add -D eslint eslint-config-next eslint-config-prettier
pnpm install   # if the monorepo already lists them
PackageRole
eslintCLI + engine
eslint-config-nextNext core-web-vitals + TypeScript recommended
eslint-config-prettierTurns off rules that fight Prettier

Config file: eslint.config.mjs (flat config).

Commands

bash
pnpm run lint
pnpm run lint:fix
pnpm exec eslint path/to/file.tsx

Implementation examples

Flat config shape

js
// eslint.config.mjs
import { defineConfig, globalIgnores } from 'eslint/config';
import nextVitals from 'eslint-config-next/core-web-vitals';
import nextTs from 'eslint-config-next/typescript';
import prettier from 'eslint-config-prettier/flat';

export default defineConfig([
  ...nextVitals,
  ...nextTs,
  prettier,
  {
    rules: {
      'no-console': 'error',
      '@typescript-eslint/no-explicit-any': ['error', { fixToUnknown: true }],
      'max-params': ['warn', { max: 3 }],
      complexity: ['warn', { max: 10 }],
      'max-lines': ['warn', { max: 250, skipBlankLines: true, skipComments: true }],
    },
  },
  {
    files: ['scripts/**/*.{js,mjs,cjs,ts}'],
    rules: { 'no-console': 'off' },
  },
  globalIgnores(['.next/**', 'studio/**', '**/.vitepress/dist/**']),
]);

Fixing common findings

ts
// Bad
console.log('debug');
function load(a: any) {
  return a;
}

// Good
function load(a: unknown) {
  if (typeof a !== 'string') throw new Error('expected string');
  return a;
}
ts
// Prefer options objects over long parameter lists
function send(options: { to: string; from: string; subject: string; body: string }) {}

Not covered by ESLint

Frontend Corner — agent ops, tooling, and decision records