React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

eslintlintingnextjsconfiguration

ESLint Setup for Next.js

Configure ESLint with the flat config format for a Next.js project using built-in presets.

Recipe

Quick-reference recipe card — copy-paste ready.

# Install ESLint (included with create-next-app)
npm install --save-dev eslint eslint-config-next
 
# Run linting via Next.js CLI
npx next lint
 
# Lint and auto-fix
npx next lint --fix
 
# Lint specific directories
npx next lint --dir src --dir app

When to reach for this: Every Next.js project should have ESLint configured from day one.

Working Example

// eslint.config.mjs
import { FlatCompat } from "@eslint/eslintrc";
import { dirname } from "path";
import { fileURLToPath } from "url";
 
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
 
const compat = new FlatCompat({
  baseDirectory: __dirname,
});
 
const eslintConfig = [
  ...compat.extends("next/core-web-vitals", "next/typescript"),
  {
    rules: {
      // Customize rules here
      "@typescript-eslint/no-unused-vars": [
        "error",
        { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
      ],
      "react/no-unescaped-entities": "off",
    },
  },
  {
    ignores: [
      "node_modules/",
      ".next/",
      "out/",
      "public/",
      "coverage/",
    ],
  },
];
 
export default eslintConfig;

What this demonstrates:

  • ESLint 9 flat config format using eslint.config.mjs
  • Using FlatCompat to bridge Next.js configs (which still use the legacy format) into flat config
  • Extending next/core-web-vitals (includes React, hooks, and import rules) and next/typescript
  • Customizing a rule and ignoring directories

Deep Dive

How It Works

  • Next.js ships with its own ESLint integration via npx next lint
  • The next/core-web-vitals preset bundles eslint-plugin-react, eslint-plugin-react-hooks, eslint-plugin-next, and eslint-plugin-import with strict defaults
  • The next/typescript preset adds @typescript-eslint/eslint-plugin rules tuned for Next.js
  • ESLint 9 uses flat config (eslint.config.mjs) instead of the legacy .eslintrc format
  • FlatCompat is a bridge that lets you use legacy extends-style configs inside flat config — necessary because Next.js presets haven't fully migrated yet

Variations

Standalone ESLint (without next lint):

# Run ESLint directly
npx eslint . --fix
 
# Or via package.json script
{
  "scripts": {
    "lint": "next lint",
    "lint:fix": "next lint --fix",
    "lint:strict": "next lint --strict"
  }
}

Strict mode (treat warnings as errors):

npx next lint --strict

Ignoring files:

// In eslint.config.mjs — ignores block
{
  ignores: [
    "**/*.config.js",
    "**/*.config.mjs",
    "migrations/",
    "generated/",
  ],
}

TypeScript Notes

# The next/typescript preset handles these automatically:
# - @typescript-eslint/parser is configured
# - TypeScript-aware rules are enabled
# - .ts and .tsx files are included by default

Gotchas

Things that will bite you. Each gotcha includes what goes wrong, why it happens, and the fix.

  • Flat config not detected — If ESLint ignores your eslint.config.mjs, you may have a leftover .eslintrc.* file. ESLint 9 prefers flat config but falls back to legacy if both exist. Fix: Delete all .eslintrc.* files when using flat config.

  • First run prompts for config — Running npx next lint for the first time shows a setup wizard. Fix: Choose "Strict" for next/core-web-vitals or create the config file manually beforehand.

  • Ignores must be a separate object — In flat config, the ignores array only works as a global ignore when it is the only key in its config object. Mixing ignores with rules in the same object makes it a filter, not a global ignore. Fix: Always put global ignores in their own { ignores: [...] } object.

  • Performance on large projectsnpx next lint only lints app/, pages/, components/, lib/, and src/ by default. Custom directories need --dir. Fix: Add --dir flags or configure eslint.dirs in next.config.js.

Alternatives

Other ways to solve the same problem — and when each is the better choice.

AlternativeUse WhenDon't Use When
BiomeYou want a single fast tool for linting and formattingYou need the full ESLint plugin ecosystem
oxlintYou want extremely fast linting written in RustYou need custom or Next.js-specific rules
eslint CLI directlyYou need full control over which files are lintedYou want Next.js-aware defaults out of the box

FAQs

Why does Next.js use FlatCompat instead of native flat config?
  • Next.js ESLint presets (next/core-web-vitals, next/typescript) still use the legacy extends format internally.
  • FlatCompat bridges these legacy configs into ESLint 9 flat config.
  • Once Next.js ships native flat config support, FlatCompat will no longer be needed.
What is the difference between next/core-web-vitals and next/typescript?
  • next/core-web-vitals bundles React, hooks, import, and Next.js-specific rules with strict defaults.
  • next/typescript adds @typescript-eslint/eslint-plugin rules tuned for Next.js.
  • Most projects extend both.
Can I use a .eslintrc file instead of eslint.config.mjs?
  • ESLint 9 prefers flat config (eslint.config.mjs).
  • If both a .eslintrc.* and eslint.config.mjs exist, ESLint falls back to the legacy file.
  • Delete all .eslintrc.* files when migrating to flat config to avoid confusion.
How do I lint directories outside the Next.js defaults?
# Next.js only lints app/, pages/, components/, lib/, src/ by default
npx next lint --dir src --dir utils --dir services
What does --strict mode do in next lint?
  • It treats all warnings as errors.
  • Any rule set to "warn" will now fail the lint run.
  • Useful for CI where you want zero tolerance for warnings.
Gotcha: Why does ESLint ignore my global ignores pattern?
  • In flat config, ignores only works as a global ignore when it is the sole key in its config object.
  • If you mix ignores with rules in the same object, it acts as a file filter, not a global ignore.
  • Always put global ignores in a separate { ignores: [...] } object.
How do I suppress unused variable errors for function parameters in TypeScript?
"@typescript-eslint/no-unused-vars": [
  "error",
  { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
]

Prefix unused parameters with _ and they will be ignored.

What is the purpose of the __filename and __dirname setup in eslint.config.mjs?
  • ES modules do not have __filename or __dirname globals.
  • fileURLToPath(import.meta.url) and dirname() recreate them.
  • FlatCompat requires baseDirectory to resolve relative config paths.
How do I type-check the ESLint config file itself?
  • eslint.config.mjs is plain JavaScript, not TypeScript.
  • You can add // @ts-check at the top and use JSDoc annotations for basic type checking.
  • Alternatively, use eslint.config.ts with experimental TypeScript config support in ESLint 9.
Gotcha: Why does the first run of npx next lint prompt me to choose a config?
  • Next.js shows a setup wizard on first run if no ESLint config file exists.
  • To avoid this, create eslint.config.mjs before running npx next lint.
  • Choose "Strict" in the wizard to get next/core-web-vitals defaults.
Can I run ESLint directly instead of through next lint?
# Direct ESLint (skips Next.js directory defaults)
npx eslint . --fix
 
# next lint (respects Next.js directory defaults)
npx next lint --fix

Use npx eslint . when you need full control over which files are linted.

How do I add TypeScript path alias support to ESLint?
  • The next/typescript preset configures the TypeScript parser automatically.
  • Path aliases like @/* defined in tsconfig.json are resolved by the parser.
  • No extra ESLint configuration is needed for path aliases in a Next.js project.