Skip to main content

ESLint NEXT.js

Update NextConfig​

Update the next.config.ts file with the following so files at the root level of the project are including when linting:

next.config.ts
const nextConfig: NextConfig = {
eslint: {
dirs: ["."],
},
};

Modify ESLint Config​

tip

There is an option for config files to be defined using typescript, eslint.config.mts. It requires jiti as an optional dev dependency. Native support for typescript without the need for jiti is in experimental as of Node.js >= 22.10.0 (See --experimental-strip-types flag).

warning

ESLint v9 has transitioned to using Flat Configuration Files. eslint-plugin-next is still transitioning to this new configuration format. ESLint's FlatCompat utility is necessary to translate the eslintrc format into flat config format.

(Last validated on 04/09/2025)

info

As of April 10th, 2025

  • The next config extends eslint-plugin-react, eslint-plugin-react-hooks
  • The next/core-web-vitals extends the next config
  • The next/typescript is a wrapper config for typescript-eslint
  • @eslint/js is NOT included in any of the next lint configs

Install the following plugins and configs:

pnpm add --save-dev @eslint/compat @eslint/js @eslint/css @eslint/json @eslint/markdown

Modify the eslint.config.mjs file to match the following:

eslint.config.mjs
import { fileURLToPath } from "node:url";
import { defineConfig } from "eslint/config";
import { includeIgnoreFile } from "@eslint/compat";
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";
// import css from "@eslint/css"; // 🟡 TODO: Does this work yet for TailwindCSS v4.0?
import json from "@eslint/json";
import markdown from "@eslint/markdown";

const gitignorePath = fileURLToPath(new URL(".gitignore", import.meta.url));
const compat = new FlatCompat({
baseDirectory: import.meta.dirname,
});

export default defineConfig([
includeIgnoreFile(gitignorePath),
{
files: ["**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}"],
extends: [
js.configs.recommended,
compat.extends("next/core-web-vitals", "next/typescript"),
],
},
// 🟡 TODO: If you are NOT using TailwindCSS then it should be safe to uncomment this object and
// remove the `languageOptions` property
// 🟡 TODO: Does this work yet for TailwindCSS v4.0?
// {
// files: ["**/*.css"],
// language: "css/css",
// languageOptions: {
// tolerant: true, // 🟡 This may be required due to custom syntax that TailwindCSS uses
// customSyntax: tailwindSyntax, // 🟡 This has not been updated yet for TailwindCSS v4.0
// },
// extends: [css.configs.recommended],
// },
{
files: ["**/*.json"],
ignores: ["package-lock.json"],
language: "json/json",
extends: [json.configs.recommended],
},
{
files: ["**/*.md"],
extends: [markdown.configs.recommended],
language: "markdown/gfm", // 🟡 Optional, include this property if using Github-flavored markdown
rules: {
"markdown/no-missing-label-refs": "off", // 🟡 Optional, this rule doesn't work with Github-flavored markdown alerts
},
},
]);

Custom Rules​

Add the following custom rules to your eslint.config.mjs config.

eslint.config.mjs
/* ... */
export default defineConfig([
includeIgnoreFile(gitignorePath),
{
files: ["**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}"],
extends: [js.configs.recommended, tseslint.configs.recommended],
rules: {
"no-console": ["error", { allow: ["warn", "error"] }],
"no-alert": "error",
"no-restricted-syntax": [
"error",
{
selector: "TSEnumDeclaration",
message:
"Enums are forbidden. Use string union types instead. Example: type Suit = 'HEARTS' | 'DIAMONDS' | 'SPADES' | 'CLUBS';",
},
],
},
},
/* ... */
]);

Docusaurus​

Docusaurus .gitignore​

Merge the ./docusaurus/.gitignore into the root .gitignore so that eslint properly ignores the ignored docusaurus files. Example:

.gitignore
# ...

# Docusaurus
/docusaurus/node_modules
/docusaurus/build
.docusaurus
.cache-loader

Docusaurus plugin​

warning

This plugin doesn't appear to be compatible with the Flat Configuration format of ESLint v9, and the rules it defines are not that important for most use cases

tip

If you want to see the final rule set, all plugins, and names of all extended configs, use one of the following commands:

  • Using npx: npx eslint --print-config src/file.tsx > tmp.config.json

  • Using pnpx: pnpx eslint --print-config src/file.tsx > tmp.config.json

VS Code Extension​

Add the following to the .vscode/extensions.json file.

.vscode/extensions.json
{
"recommendations": [
. . .
"dbaeumer.vscode-eslint",
]
}

VS Code Settings​

Add the following to the .vscode/settings.json file.

.vscode/settings.json
{
"eslint.useFlatConfig": true
}

Project Documentation​

Update 01-scripts.md Documentation​

Add or update the following to the docusaurus/docs/01-scripts.md documentation

docusaurus/docs/01-scripts.md
| `pnpm lint` | Runs ESLint for all files that are NOT included in the `.gitignore` |

Create 02-code-quality/02-eslint.md Documentation​

Create a docusaurus/docs/02-code-quality/02-eslint.md file and add the following:

docusaurus/docs/02-code-quality/02-eslint.md
# ESLint

## What is ESLint

[ESLint](https://eslint.org/) is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs.

## No Enums Custom Rule

Our eslint config defines a custom rule that forbids the use of `enums`. The reason for this rule is best explained by this article: [Why you should use string literal unions over enums in TypeScript](../99-articles/why-you-should-use-string-literal-unions/index.md).

## Print Full Config

If you want to see the final rule set, all plugins, and names of all extended configs, use one of the following commands:

- Using npx: `npx eslint --print-config src/file.tsx > tmp.config.json`

- Using pnpx: `pnpx eslint --print-config src/file.tsx > tmp.config.json`

## Disable Node

To disable a line of code do the following:

```js
// Explanation of why there is a disable statement
// eslint-disable-next-line no-console
console.log("bar");
```

To disable a block of code do the following:

```js
// Explanation of why there is a disable statement
/* eslint-disable no-console */
console.log("bar");
/* eslint-enable no-console */
```

## Disable Linting Conventions

Please follow these 2 conventions When disabling a line or block of code:

**Convention 1** - Only disable the offending rule, not all of eslint. Example:

**Good** = `/* eslint disable no-console */`
**Bad** = `/* eslint disable */`

**Convention 2** - Provide a description explaining why the rule is disabled

The description must come after the configuration and needs to be separated from the configuration by two or more consecutive - characters. For example:

```js
// eslint-disable-next-line no-console -- Here's a description about why this configuration is necessary.
console.log("hello");

/* eslint-disable-next-line no-console --
* Here's a very long description about why this configuration is necessary
* along with some additional information
**/
console.log("hello");
```

## Ignore root files or directories

See ESLint documentation on [Ignoring Files and Directories](https://eslint.org/docs/latest/use/configure/ignore)

Update 98-vs-code/02-vs-code-extensions.md Documentation​

Add the follow to the docusaurus/docs/98-vs-code/02-vs-code-extensions.md documentation

docusaurus/docs/98-vs-code/02-vs-code-extensions.md
| [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) | Provides immediate linting when writing code |

Create 99-articles/why-you-should-use-string-literal-unions Article​

note

I'm paranoid about broken links due to relevant articles being removed. Therefore, when I find an article that I think is relevant to a project, I like to save a copy of that article within the project's documentation.

info

The 02-code-quality/02-eslint.md documentation links to the following article

  1. Create a docusaurus/docs/99-articles/why-you-should-use-string-literal-unions directory.
  2. Copy the Why You Should Use String Literal Unions Over Enums in TypeScript article contents into the above directory.