Essential ESLint Rules
Configure the most impactful ESLint rules for React, hooks, TypeScript, imports, and accessibility.
Search across all documentation pages
Configure the most impactful ESLint rules for React, hooks, TypeScript, imports, and accessibility.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Quick-reference recipe card - copy-paste ready.
// Severity levels
"off" // 0 - disable the rule
"warn" // 1 - yellow warning, does not fail CI
"error" // 2 - red error, fails CI and blocks builds
// Common pattern: override a rule
{
rules: {
"rule-name": "error",
"rule-name": ["error", { option: "value" }],
},
}When to reach for this: When the default presets are too loose or too strict and you need to fine-tune specific rules.
// 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: {
// --- React rules ---
"react/jsx-no-target-blank": "error",
"react/no-unescaped-entities": "off",
"react/self-closing-comp": "warn",
"react/jsx-curly-brace-presence": [
"warn",
{ props: "never", children: "never" },
],
// --- Hooks rules ---
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
// --- TypeScript rules ---
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/consistent-type-imports": [
"error",
{ prefer: "type-imports" },
],
// --- Import rules ---
"import/order": [
"warn",
{
groups: [
"builtin",
"external",
"internal",
["parent", "sibling"],
"index",
"type",
],
"newlines-between": "always",
alphabetize: { order: "asc", caseInsensitive: true },
},
],
"import/no-duplicates": "error",
// --- Accessibility rules ---
"jsx-a11y/alt-text": "error",
"jsx-a11y/anchor-is-valid": "warn",
},
},
];
export default eslintConfig;What this demonstrates:
error (must fix) and warn (should fix) severities_"off", "warn", or "error"["error", { option: true }]next/core-web-vitals preset already enables many rules - you override them in your own configreact/, @typescript-eslint/)React rules worth knowing:
| Rule | What It Catches |
|---|---|
react/jsx-no-target-blank | Missing rel="noreferrer" on target="_blank" links |
react/no-unescaped-entities | Unescaped ' or " in JSX text |
react/self-closing-comp | <div></div> instead of <div /> for empty elements |
react/jsx-curly-brace-presence | Unnecessary {"string"} instead of string in JSX |
react/no-array-index-key | Using array index as key prop |
Hooks rules:
| Rule | What It Catches |
|---|---|
react-hooks/rules-of-hooks | Hooks called conditionally or in loops |
react-hooks/exhaustive-deps | Missing dependencies in useEffect, useMemo, useCallback |
TypeScript rules worth enabling:
| Rule | What It Catches |
|---|---|
@typescript-eslint/no-unused-vars | Declared but unused variables |
@typescript-eslint/no-explicit-any | Using any type |
@typescript-eslint/consistent-type-imports | Missing type keyword on type-only imports |
@typescript-eslint/no-non-null-assertion | Using ! non-null assertion |
@typescript-eslint/prefer-nullish-coalescing | Using logical OR instead of ?? |
// consistent-type-imports enforces this:
import type { User } from "@/types"; // type-only import
import { fetchUser } from "@/lib/api"; // value import
// Instead of mixing them:
import { User, fetchUser } from "@/lib/api"; // ❌ lint errorThings that will bite you. Each gotcha includes what goes wrong, why it happens, and the fix.
exhaustive-deps false positives - This rule sometimes flags stable references like dispatch or refs. Fix: Use // eslint-disable-next-line react-hooks/exhaustive-deps only when you are certain the dependency is stable. Never disable it globally.
no-unused-vars conflicts with TypeScript - The base ESLint no-unused-vars and @typescript-eslint/no-unused-vars can conflict. Fix: Turn off the base rule and only use the TypeScript version: "no-unused-vars": "off".
import/order not auto-fixing - The rule reports violations but the --fix only works for reordering, not adding newlines between groups retroactively. Fix: Run eslint --fix and manually add blank lines on the first pass.
Severity matters for CI - Using "warn" means CI passes even with violations. If you want to enforce a rule, use "error". Fix: Reserve "warn" for rules you are migrating toward, use "error" for enforced rules.
Other ways to solve the same problem - and when each is the better choice.
| Alternative | Use When | Don't Use When |
|---|---|---|
next/core-web-vitals defaults | You want sensible defaults without customization | You need stricter or project-specific rules |
| Biome lint rules | You want faster linting with built-in rules | You need the full breadth of ESLint plugins |
TypeScript compiler (tsc --noEmit) | You want type-level checks that ESLint cannot do | You need code style or pattern enforcement |
"off" (0) disables the rule completely."warn" (1) shows a yellow warning but does not fail CI or block builds."error" (2) shows a red error, fails CI, and blocks builds.no-unused-vars does not understand TypeScript syntax (interfaces, type aliases, enums)."no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],// Enforced (correct):
import type { User } from "@/types";
import { fetchUser } from "@/lib/api";
// Rejected (lint error):
import { User, fetchUser } from "@/lib/api";It separates type-only imports from value imports so bundlers can tree-shake types.
"newlines-between": "always".eslint --fix can reorder imports within groups.eslint --fix once, then manually add blank lines on the initial pass."warn" for rules you are migrating toward or that are advisory."error" for rules you want to enforce strictly in CI."warn" will not fail CI, so violations accumulate silently if you forget to promote to "error".useEffect, useMemo, and useCallback dependency arrays."warn" because it can produce false positives with stable references like dispatch.// eslint-disable-next-line react-hooks/exhaustive-deps only when you are certain the dependency is stable.target="_blank" without rel="noreferrer" expose your page to window.opener attacks.rel="noreferrer" to all external links.// Severity only:
"rule-name": "error"
// Severity with options:
"rule-name": ["error", { option: "value" }]The second element of the array is the rule's options object.
| Rule | Purpose |
|---|---|
@typescript-eslint/no-explicit-any | Flags any usage |
@typescript-eslint/no-non-null-assertion | Flags ! assertions |
@typescript-eslint/prefer-nullish-coalescing | Prefers ?? over || |
@typescript-eslint/consistent-type-imports | Enforces import type |
rules object after the extends array in your flat config.const eslintConfig = [
...compat.extends("next/core-web-vitals"),
{ rules: { "react/no-unescaped-entities": "off" } },
];Reviewed by Chris St. John·Last updated Jul 16, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥