---
title: "ESLint Rules"
description: "The @next/eslint-plugin-next package provides custom static analysis rules designed specifically for Next.js applications. It enforces performance best practices, proper layout structures, and corr..."
last_updated: "2026-09-23T10:52:03.135025+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/ecosystem-packages/eslint-rules"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/eslint-plugin-next/src/rules/no-page-custom-font.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts)
- [packages/eslint-plugin-next/src/index.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/index.ts)
- [packages/eslint-plugin-next/src/rules/no-img-element.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-img-element.ts)
- [packages/eslint-plugin-next/src/rules/google-font-preconnect.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/google-font-preconnect.ts)
- [packages/next/errors.json](https://github.com/blade47/next.js/blob/main/packages/next/errors.json)
- [packages/eslint-plugin-next/src/rules/google-font-display.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/google-font-display.ts)
- [packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts)
- [packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts)
- [packages/eslint-plugin-next/src/rules/next-script-for-ga.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/next-script-for-ga.ts)
- [packages/eslint-plugin-next/src/rules/no-script-component-in-head.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-script-component-in-head.ts)
- [packages/next/src/pages/_document.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/pages/_document.tsx)
- [packages/eslint-plugin-next/src/rules/no-head-element.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-head-element.ts)
- [packages/eslint-plugin-next/src/rules/no-css-tags.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-css-tags.ts)
- [packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts)
- [packages/next/src/server/typescript/rules/config.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/typescript/rules/config.ts)
- [packages/eslint-plugin-next/src/rules/no-sync-scripts.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-sync-scripts.ts)
- [packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts)
- [packages/eslint-config-next/src/index.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-config-next/src/index.ts)
- [packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts)
- [packages/next/src/server/config.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/config.ts)
- [packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts)
- [packages/next/src/client/legacy/image.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/client/legacy/image.tsx)
- [packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts)
- [packages/eslint-plugin-next/src/rules/inline-script-id.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/inline-script-id.ts)
- [packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts)
- [packages/eslint-plugin-next/src/rules/no-duplicate-head.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-duplicate-head.ts)
- [packages/next-codemod/transforms/__testfixtures__/next-lint-to-eslint-cli/legacy-config/.eslintrc.json](https://github.com/blade47/next.js/blob/main/packages/next-codemod/transforms/__testfixtures__/next-lint-to-eslint-cli/legacy-config/.eslintrc.json)
- [packages/next/src/shared/lib/get-img-props.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/get-img-props.ts)
- [packages/font/local/index.js](https://github.com/blade47/next.js/blob/main/packages/font/local/index.js)
- [packages/eslint-plugin-next/src/rules/no-typos.ts](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-typos.ts)
</details>

## Overview

### Overview

The `@next/eslint-plugin-next` package provides custom static analysis rules designed specifically for Next.js applications. It enforces performance best practices, proper layout structures, and correct API usage across fonts, images, scripts, and document structure. Operating directly on Abstract Syntax Trees (ASTs) parsed from source files, these rules bridge the gap between generic React linting and Next.js framework constraints, preventing common anti-patterns that harm Core Web Vitals, server-side rendering (SSR), and Largest Contentful Paint (LCP).

Sources: [packages/eslint-plugin-next/src/index.ts:1-127](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/index.ts#L1-L127)

By integrating with standard ESLint configurations (`@next/next/recommended` and `@next/next/core-web-vitals`), the plugin inspects JSX structures, import declarations, file paths, and exported module members. It addresses performance bottlenecks such as unoptimized `<img>` tags and manual stylesheet inclusions, architectural errors like importing `next/document` inside standard pages, and reliability issues such as typos in data-fetching functions.

Sources: [packages/eslint-plugin-next/src/index.ts:1-127](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/index.ts#L1-L127)

---

## Plugin Architecture and Configuration Integration

The `@next/eslint-plugin-next` package exports an ESLint plugin object containing a metadata block, a map of all implemented rule modules, and pre-bundled configuration sets. The two primary rule sets exposed by the plugin are `recommendedRules` and `coreWebVitalsRules`.

Sources: [packages/eslint-plugin-next/src/index.ts:1-56](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/index.ts#L1-L56)

Each rule in the plugin is built using a helper utility `defineRule` and defines a `meta` property outlining its description, documentation URL, problem type, and an empty schema `[]`. Rules are registered inside the plugin object under kebab-case names. The main configuration suite combines these rules into distinct presets for modern flat configuration files (`Linter.Config`) and legacy configurations (`Linter.LegacyConfig`).

Sources: [packages/eslint-plugin-next/src/index.ts:58-127](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/index.ts#L58-L127)

```mermaid
flowchart TD
    A["eslint-config-next"] --> B["@next/eslint-plugin-next"]
    B --> C["recommended"]
    B --> D["core-web-vitals"]
    C --> E["Performance Warnings<br>(google-font-display, no-img-element, etc.)"]
    C --> F["Strict Errors<br>(inline-script-id, no-document-import-in-page, etc.)"]
    D --> G["Core Web Vitals Errors<br>(no-html-link-for-pages, no-sync-scripts)"]
```

Sources: [packages/eslint-plugin-next/src/index.ts:26-56](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/index.ts#L26-L56)

---

## Font Optimization and Loading Rules

To guarantee optimal font rendering and prevent layout shifts (CLS), the plugin enforces precise structuring of Google Fonts and custom fonts via three rules: `google-font-display`, `google-font-preconnect`, and `no-page-custom-font`.

Sources: [packages/eslint-plugin-next/src/rules/google-font-display.ts:1-62](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/google-font-display.ts#L1-L62)

The `google-font-display` rule inspects `<link>` JSX opening elements. If the `href` attribute starts with `https://fonts.googleapis.com/css`, it parses query parameters to verify that the `display` parameter is present and set to a recommended value rather than `auto`, `block`, or `fallback`. Similarly, `google-font-preconnect` verifies that links pointing to `https://fonts.gstatic.com` include the `rel="preconnect"` attribute.

Sources: [packages/eslint-plugin-next/src/rules/google-font-display.ts:18-60](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/google-font-display.ts#L18-L60), [packages/eslint-plugin-next/src/rules/google-font-preconnect.ts:18-45](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/google-font-preconnect.ts#L18-L45)

The `no-page-custom-font` rule checks files within the `pages` directory. If a custom Google Font link tag is included outside of `pages/_document.js` or outside the default document component, it reports an error warning that the font will only load for a single page or disable automatic font optimization.

Sources: [packages/eslint-plugin-next/src/rules/no-page-custom-font.ts:22-170](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts#L22-L170)

> [!NOTE]
> `google-font-display` recommends appending `&display=optional` to Google Font URLs to prevent flash of invisible text (FOIT) and layout shifts.

Sources: [packages/eslint-plugin-next/src/rules/google-font-display.ts:39-42](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/google-font-display.ts#L39-L42)

---

## Image Performance and Element Rules

The `no-img-element` rule prevents the usage of native HTML `<img>` elements, which lack automatic sizing, modern format conversion, and responsive srcset generation, leading to slower Largest Contentful Paint (LCP) and higher bandwidth consumption.

Sources: [packages/eslint-plugin-next/src/rules/no-img-element.ts:6-17](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-img-element.ts#L6-L17)

The rule analyzes `JSXOpeningElement` nodes. When it encounters an `img` tag, it applies several conditional guards before reporting an infraction: checking if the file resides in the `app` directory, ignoring metadata route files, and ignoring `img` elements wrapped inside a `icture>` parent component.

Sources: [packages/eslint-plugin-next/src/rules/no-img-element.ts:18-55](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-img-element.ts#L18-L55)

```typescript
// Example triggering no-img-element
export default function Profile() {
  return <img src="/profile.png" alt="Profile" />
}
```
Sources: [packages/eslint-plugin-next/src/rules/no-img-element.ts:49-52](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-img-element.ts#L49-L52)

---

## Script Strategy and Third-Party Integration Rules

Script loading is strictly governed by rules preventing synchronous blocking scripts, missing script identifiers, and misconfigured third-party integrations.

Sources: [packages/eslint-plugin-next/src/rules/no-sync-scripts.ts:5-15](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-sync-scripts.ts#L5-L15)

The `no-sync-scripts` rule flags any `<script src="...">` element that lacks either an `async` or `defer` attribute. The `inline-script-id` rule ensures that any `next/script` component containing inline content (either via JSX children or `dangerouslySetInnerHTML`) explicitly specifies an `id` attribute, preventing hydration mismatches and duplicate injection.

Sources: [packages/eslint-plugin-next/src/rules/no-sync-scripts.ts:16-40](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-sync-scripts.ts#L16-L40), [packages/eslint-plugin-next/src/rules/inline-script-id.ts:5-75](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/inline-script-id.ts#L5-L75)

The `next-script-for-ga` rule detects raw Google Analytics or Google Tag Manager script elements (matching `www.google-analytics.com/analytics.js`, `www.googletagmanager.com/gtag/js`, or `www.googletagmanager.com/gtm.js`) and instructs developers to prefer `@next/third-parties/google`.

Sources: [packages/eslint-plugin-next/src/rules/next-script-for-ga.ts:16-83](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/next-script-for-ga.ts#L16-L83)

| Rule Name | Target Element / Source | Severity | Purpose |
| :--- | :--- | :--- | :--- |
| `no-sync-scripts` | `<script src>` | Error (Core Web Vitals) | Prevents blocking synchronous script execution. |
| `inline-script-id` | `<Script>` (inline) | Error | Requires an `id` prop for tracking inline scripts. |
| `next-script-for-ga` | Analytics scripts | Warning | Recommends `@next/third-parties/google`. |
| `no-unwanted-polyfillio` | Polyfill.io CDN | Warning | Prevents redundant polyfills already bundled in Next.js. |

Sources: [packages/eslint-plugin-next/src/index.ts:26-56](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/index.ts#L26-L56), [packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts:76-144](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts#L76-L144)

---

## Document Structure and Boundary Enforcement

Custom document files (`pages/_document.js` or `pages/_document.tsx`) have rigid architectural requirements. The plugin enforces these through specialized boundary rules.

Sources: [packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts:6-16](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts#L6-L16)

- `no-document-import-in-page`: Prevents importing `next/document` outside of `pages/_document.js`.
- `no-head-import-in-document`: Prevents importing `next/head` inside `pages/_document.js`, requiring `Head` from `next/document` instead.
- `no-title-in-document-head`: Disallows `<title>` elements inside `<Head>` from `next/document`, mandating page-level titles via `next/head`.

Sources: [packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts:17-42](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts#L17-L42), [packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts:16-42](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts#L16-L42), [packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts:15-55](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts#L15-L55)

- `no-duplicate-head`: Ensures multiple instances of `<Head />` are not rendered inside `pages/_document.js`.
- `no-styled-jsx-in-document`: Prevents usage of `styled-jsx` (`<style jsx>`) in document files.
- `no-before-interactive-script-outside-document`: Restricts `next/script` with `beforeInteractive` strategy exclusively to `pages/_document.js` when working in the `pages` directory.

Sources: [packages/eslint-plugin-next/src/rules/no-duplicate-head.ts:4-68](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-duplicate-head.ts#L4-L68), [packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts:6-48](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts#L6-L48), [packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts:10-72](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts#L10-L72)

> [!CAUTION]
> Importing `next/document` in a regular page component breaks server-side rendering execution order and markup injection.

Sources: [packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts:35-38](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts#L35-L38)

---

## Routing and Navigation Lint Rules

To encourage client-side routing performance and prevent full-page reloads, rules target navigation mechanisms.

Sources: [packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts:38-66](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts#L38-L66)

The `no-html-link-for-pages` rule scans `<a>` elements for internal page navigation. It resolves app and pages directories (`foundPagesDirs`, `foundAppDirs`) and compiles regular expressions matching internal routes.

Sources: [packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts:71-113](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts#L71-L113)

If an `<a>` tag links to an internal route without being a target `_blank` or having a `download` attribute, the rule reports an error advising the use of `<Link />` from `next/link`.

Sources: [packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts:114-165](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts#L114-L165)

The `no-location-assign-relative-destination` rule detects `location.assign()` and `location.href` assignments. If the destination is a relative or internal URL (lacking `://`), it blocks execution and recommends `redirect()` in the render phase or `useRouter().push()` in event handlers.

Sources: [packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts:46-113](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts#L46-L113)

---

## Data Fetching Typos and Error Prevention

The `no-typos` rule prevents common misspellings in Next.js data-fetching function exports. It checks named exports in page files outside of API routes against the standard set: `getStaticProps`, `getStaticPaths`, and `getServerSideProps`.

Sources: [packages/eslint-plugin-next/src/rules/no-typos.ts:43-52](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-typos.ts#L43-L52)

The matching mechanism computes the Levenshtein distance (`minDistance(a, b)`) between export identifiers and valid Next.js functions. If the calculated distance is within the threshold (`THRESHOLD = 1`) and greater than zero, the rule reports a potential typo.

Sources: [packages/eslint-plugin-next/src/rules/no-typos.ts:14-41](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-typos.ts#L14-L41), [packages/eslint-plugin-next/src/rules/no-typos.ts:53-109](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-typos.ts#L53-L109)

```typescript
// Example triggering no-typos error
export async function getStaticProp() {
  return { props: { data: null } }
}
```
Sources: [packages/eslint-plugin-next/src/rules/no-typos.ts:67-71](https://github.com/blade47/next.js/blob/main/packages/eslint-plugin-next/src/rules/no-typos.ts#L67-L71)

## Related

- [TypeScript Plugin](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-and-config/typescript-plugin)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
