---
title: "ESLint Rules"
description: "The Next.js ESLint integration provides specialized linting rules through [[ESLint Rules]] in packages like @next/eslint-plugin-next and [[eslint-config-next]] to enforce framework best practices, ..."
last_updated: "2026-10-12T06:38:56.853737+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/companion-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-img-element.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-img-element.ts)
- [packages/eslint-plugin-next/src/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts)
- [packages/next-codemod/transforms/next-lint-to-eslint-cli.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next-codemod/transforms/next-lint-to-eslint-cli.ts)
- [packages/eslint-plugin-next/src/rules/no-page-custom-font.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts)
- [packages/eslint-plugin-next/src/rules/no-sync-scripts.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-sync-scripts.ts)
- [packages/eslint-plugin-next/src/rules/no-script-component-in-head.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-script-component-in-head.ts)
- [packages/eslint-plugin-next/src/rules/no-head-element.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-head-element.ts)
- [packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts)
- [packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts)
- [packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts)
- [packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts)
- [packages/eslint-plugin-next/src/rules/no-css-tags.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/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/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts)
- [packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/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/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-duplicate-head.ts)
- [packages/eslint-plugin-next/src/rules/google-font-preconnect.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/google-font-preconnect.ts)
- [packages/eslint-plugin-next/src/rules/next-script-for-ga.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/next-script-for-ga.ts)
- [packages/eslint-plugin-next/src/rules/google-font-display.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/google-font-display.ts)
- [packages/eslint-config-next/src/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-config-next/src/index.ts)
- [packages/next/src/next-devtools/dev-overlay/container/errors.tsx](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/next-devtools/dev-overlay/container/errors.tsx)
- [packages/next/src/client/legacy/image.tsx](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/legacy/image.tsx)
- [packages/eslint-plugin-next/src/rules/inline-script-id.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/inline-script-id.ts)
- [packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts)
- [packages/eslint-plugin-internal/src/eslint-no-ambiguous-jsx.js](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-internal/src/eslint-no-ambiguous-jsx.js)
- [packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts)
- [packages/next/src/build/webpack-config.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack-config.ts)
- [packages/eslint-plugin-next/src/rules/no-async-client-component.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-async-client-component.ts)
- [packages/next/src/server/config.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/config.ts)
- [packages/next/src/build/babel/plugins/next-page-config.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/babel/plugins/next-page-config.ts)
- [packages/eslint-config-next/src/core-web-vitals.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-config-next/src/core-web-vitals.ts)
</details>

## Overview

The Next.js ESLint integration provides specialized linting rules through `[[ESLint Rules]]` in packages like `@next/eslint-plugin-next` and `[[eslint-config-next]]` to enforce framework best practices, optimize performance [packages/eslint-plugin-next/src/rules/no-img-element.ts:7-14](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-img-element.ts#L7-L14), and prevent common configuration errors [packages/eslint-plugin-next/src/index.ts:26-51](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L26-L51). By validating static Abstract Syntax Trees (AST) against expected Next.js conventions, these rules catch improper component imports, suboptimal asset loading, and incorrect document structure at development time [[packages/eslint-plugin-next/src/rules/no-page-custom-font.ts:12-21](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts#L12-L21), [packages/eslint-plugin-next/src/index.ts:18-22](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L18-L22). The plugin exports predefined configurations such as `recommended` and `core-web-vitals` that seamlessly integrate with standard and flat ESLint setups to maintain runtime efficiency and Core Web Vitals compliance [packages/eslint-plugin-next/src/index.ts:90-124](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L90-L124). Sources: [packages/eslint-plugin-next/src/rules/no-img-element.ts:7-14](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-img-element.ts#L7-L14), [packages/eslint-plugin-next/src/index.ts:26-51](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L26-L51), [packages/eslint-plugin-next/src/rules/no-page-custom-font.ts:12-21](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts#L12-L21), [packages/eslint-plugin-next/src/index.ts:18-22](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L18-L22), [packages/eslint-plugin-next/src/index.ts:90-124](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L90-L124)

## Plugin Architecture and Rule Registration

### Rule Index Registration

The `@next/eslint-plugin-next` package centralizes its diagnostic rules by importing individual rule modules and registering them under the `rules` dictionary of the exported plugin object. The plugin exposes 24 distinct rules covering font attributes, script boundaries, document structure, and asset optimizations. These rules are categorized into warning-level recommendations, error-level restrictions, and core web vitals strict assertions.

```typescript
const plugin = {
  meta: {
    name: '@next/eslint-plugin-next',
  },
  rules: {
    'google-font-display': googleFontDisplay,
    'google-font-preconnect': googleFontPreconnect,
    'inline-script-id': inlineScriptId,
    'next-script-for-ga': nextScriptForGa,
    'no-assign-module-variable': noAssignModuleVariable,
    'no-async-client-component': noAsyncClientComponent,
    'no-before-interactive-script-outside-document':
      noBeforeInteractiveScriptOutsideDocument,
    'no-css-tags': noCssTags,
    'no-document-import-in-page': noDocumentImportInPage,
    'no-duplicate-head': noDuplicateHead,
    'no-head-element': noHeadElement,
    'no-head-import-in-document': noHeadImportInDocument,
    'no-html-link-for-pages': noHtmlLinkForPages,
    'no-img-element': noImgElement,
    'no-location-assign-relative-destination': noLocationAssign,
    'no-page-custom-font': noPageCustomFont,
    'no-script-component-in-head': noScriptComponentInHead,
    'no-styled-jsx-in-document': noStyledJsxInDocument,
    'no-sync-scripts': noSyncScripts,
    'no-title-in-document-head': noTitleInDocumentHead,
    'no-typos': noTypos,
    'no-unwanted-polyfillio': noUnwantedPolyfillio,
  } satisfies Record<string, Rule.RuleModule>,
  configs: {} as ESLintPluginConfigs,
}
```
Sources: [packages/eslint-plugin-next/src/index.ts:58-88](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L58-L88)

### Configuration Profiles and Legacy Compatibility

The plugin defines both modern flat configurations and legacy configuration objects via `Object.assign` on `plugin.configs`. The `recommended` configuration applies standard framework warnings and errors, while `core-web-vitals` merges `recommendedRules` with strict performance assertions for metrics such as unoptimized anchor links and synchronous scripts.

| Configuration Name | Target Environment | Included Rules Source |
| :--- | :--- | :--- |
| `recommended-legacy` | Legacy ESLint (`plugins: ['@next/next']`) | `recommendedRules` |
| `core-web-vitals-legacy` | Legacy ESLint (extends recommended-legacy) | `recommendedRules` + `coreWebVitalsRules` |
| `recommended` | Modern Flat Config (`plugins: { '@next/next': plugin }`) | `recommendedRules` |
| `core-web-vitals` | Modern Flat Config | `recommendedRules` + `coreWebVitalsRules` |

Sources: [packages/eslint-plugin-next/src/index.ts:90-124](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L90-L124)

> [!NOTE]
> The `core-web-vitals` configuration overrides specific rules like `@next/next/no-html-link-for-pages` and `@next/next/no-sync-scripts` from warning severity to strict errors to protect loading performance metrics.

Sources: [packages/eslint-plugin-next/src/index.ts:53-56](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L53-L56), [packages/eslint-plugin-next/src/index.ts:114-123](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/index.ts#L114-L123)

### Shared Configuration and Base Setup

The `[[eslint-config-next]]` package coordinates multiple third-party parser and plugin integrations alongside `@next/eslint-plugin-next`. It establishes global file matching patterns for JavaScript and TypeScript source files (`**/*.{js,jsx,mjs,ts,tsx,mts,cts}`), configures Babel parsing settings with top-level await support, and registers resolvers for `eslint-plugin-import`.

```typescript
const config: Linter.Config[] = [
  {
    name: 'next',
    files: ['**/*.{js,jsx,mjs,ts,tsx,mts,cts}'],
    plugins: {
      react: fixupPluginRules(react),
      'react-hooks': reactHooks as ESLint.Plugin,
      import: fixupPluginRules(importPlugin),
      'jsx-a11y': jsxA11yPlugin,
      '@next/next': next,
    },
    languageOptions: {
      parser: eslintParser,
      parserOptions: {
        requireConfigFile: false,
        sourceType: 'module',
        allowImportExportEverywhere: true,
        babelOptions: {
          presets: ['next/babel'],
          caller: {
            supportsTopLevelAwait: true,
          },
        },
      },
      globals: {
        ...globals.browser,
        ...globals.node,
      },
    },
    settings: {
      react: {
        version: 'detect',
      },
      'import/parsers': {
        '@typescript-eslint/parser': ['.ts', '.mts', '.cts', '.tsx', '.d.ts'],
      },
      'import/resolver': {
        [require.resolve('eslint-import-resolver-node')]: {
          extensions: ['.js', '.jsx', '.ts', '.tsx'],
        },
        [require.resolve('eslint-import-resolver-typescript')]: {
          alwaysTryTypes: true,
        },
      },
    },
    rules: {
      ...react.configs.recommended.rules,
      ...reactHooks.configs.recommended.rules,
      ...next.configs.recommended.rules,
      'import/no-anonymous-default-export': 'warn',
      'react/no-unknown-property': 'off',
      'react/react-in-jsx-scope': 'off',
      'react/prop-types': 'off',
      'jsx-a11y/alt-text': [
        'warn',
        {
          elements: ['img'],
      img: ['Image'],
        },
      ],
      'jsx-a11y/aria-props': 'warn',
      'jsx-a11y/aria-proptypes': 'warn',
      'jsx-a11y/aria-unsupported-elements': 'warn',
      'jsx-a11y/role-has-required-aria-props': 'warn',
      'jsx-a11y/role-supports-aria-props': 'warn',
      'react/jsx-no-target-blank': 'off',
    },
  },
  {
    name: 'next/typescript',
    files: ['**/*.ts', '**/*.tsx'],
    plugins: {
      '@typescript-eslint': tsEslint.plugin,
    },
    languageOptions: {
      parser: tsEslint.parser,
      parserOptions: {
        sourceType: 'module',
      },
    },
  },
  {
    ignores: [
      '.next/**',
      'out/**',
      'build/**',
      'next-env.d.ts',
    ],
  },
]
```
Sources: [packages/eslint-config-next/src/index.ts:17-117](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-config-next/src/index.ts#L17-L117)

> [!WARNING]
> Configuration modules for `eslint-config-next` and its core web vitals extension export using CommonJS `export = config` syntax rather than ES module `export default` to maintain compatibility with legacy ESLint parser loading mechanisms.

Sources: [packages/eslint-config-next/src/index.ts:119-121](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-config-next/src/index.ts#L119-L121), [packages/eslint-config-next/src/core-web-vitals.ts:10-12](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-config-next/src/core-web-vitals.ts#L10-L12)

## Script Optimization and Polyfill Detection

### Script Loading and Placement Rules

Lint rules in `eslint-plugin-next` enforce optimal script loading strategies, mandate unique identifiers for inline scripts, restrict third-party polyfill injections, and flag suboptimal Google Analytics or Tag Manager implementations.

The `no-sync-scripts` rule intercepts raw HTML `<script>` tags that specify a `src` attribute without accompanying `async` or `defer` properties, protecting page load performance from render-blocking network requests.
Sources: [packages/eslint-plugin-next/src/rules/no-sync-scripts.ts:17-37](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-sync-scripts.ts#L17-L37)

The `no-script-component-in-head` rule traverses the AST to detect `next/script` (`<Script>`) components nested inside `next/head` (`<Head>`), reporting an error because script loading strategies must operate outside of document head tags.
Sources: [packages/eslint-plugin-next/src/rules/no-script-component-in-head.ts:27-53](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-script-component-in-head.ts#L27-L53)

The `no-before-interactive-script-outside-document` rule ensures that `next/script` instances utilizing the `beforeInteractive` loading strategy are confined to `pages/_document.js` when working within the `pages` router architecture, automatically bypassing checks when code resides inside the `app/` directory.
Sources: [packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts:30-69](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts#L30-L69)

> [!WARNING]
> The `no-before-interactive-script-outside-document` check inspects file paths via `context.filename`, converting slashes using `path.sep` to verify if the file sits in an `app` directory, and skips validation entirely for [App Router](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/app-router) components.
Sources: [packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts:31-38](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts#L31-L38)

### Inline Script Validation and Third-Party Integrations

The `inline-script-id` rule verifies that any `next/script` component containing inline children or a `dangerouslySetInnerHTML` attribute explicitly defines an `id` attribute, ignoring elements with uncheckable JSX spread attributes.
Sources: [packages/eslint-plugin-next/src/rules/inline-script-id.ts:25-72](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/inline-script-id.ts#L25-L72)

The `next-script-for-ga` rule detects raw script tags or inline blocks loading Google Analytics or Google Tag Manager URLs, prompting developers to adopt `@next/third-parties/google` instead.
Sources: [packages/eslint-plugin-next/src/rules/next-script-for-ga.ts:28-80](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/next-script-for-ga.ts#L28-L80)

| Integration Target | URL Signatures Checked | Recommendation Message |
| :--- | :--- | :--- |
| Google Analytics | `www.google-analytics.com/analytics.js` | Prefer `GoogleAnalytics` component from `@next/third-parties/google` when using the inline script for Google Analytics. |
| Google Tag Manager | `www.googletagmanager.com/gtag/js`, `www.googletagmanager.com/gtm.js` | Prefer `GoogleTagManager` component from `@next/third-parties/google` when using the inline script for Google Tag Manager. |

Sources: [packages/eslint-plugin-next/src/rules/next-script-for-ga.ts:4-8](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/next-script-for-ga.ts#L4-L8), [packages/eslint-plugin-next/src/rules/next-script-for-ga.ts:13-14](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/next-script-for-ga.ts#L13-L14)

The `no-unwanted-polyfillio` rule inspects script sources matching recognized Polyfill.io and CDN endpoints, parsing requested query features against the built-in Next.js polyfill manifest to prevent redundant network payloads.
Sources: [packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts:97-141](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts#L97-L141)

```typescript
if (
  src.startsWith('https://cdn.polyfill.io/v2/') ||
  src.startsWith('https://polyfill.io/v3/') ||
  // https://community.fastly.com/t/new-options-for-polyfill-io-users/2540
  src.startsWith('https://polyfill-fastly.net/') ||
  src.startsWith('https://polyfill-fastly.io/') ||
  // https://blog.cloudflare.com/polyfill-io-now-available-on-cdnjs-reduce-your-supply-chain-risk
  src.startsWith('https://cdnjs.cloudflare.com/polyfill/')
) {
```
Sources: [packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts:116-124](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts#L116-L124)

> [!NOTE]
> The `no-unwanted-polyfillio` rule checks requests originating from `https://cdn.polyfill.io/v2/`, `https://polyfill.io/v3/`, Fastly mirrors (`https://polyfill-fastly.net/`, `https://polyfill-fastly.io/`), and Cloudflare CDN paths (`https://cdnjs.cloudflare.com/polyfill/`).
Sources: [packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts:116-124](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts#L116-L124)

## Document and Head Tag Rules

### Head Component and Document Structure Rules

The `eslint-plugin-next` package provides AST validation rules to ensure correct usage of document-level tags and components within the Pages router architecture. These rules prevent misuse of native HTML elements like `<head>` in pages, restrict `next/document` imports and stylesheet tags, and protect against duplicate `Head` declarations or forbidden elements in custom documents.

| Rule Name | Description | Target File Context |
| :--- | :--- | :--- |
| `no-head-element` | Prevent usage of native `<head>` element, recommending `next/head` instead. | Pages router files (bypasses `app/`) |
| `no-document-import-in-page` | Prevent importing `next/document` outside of `pages/_document.js`. | Any file outside `pages/_document.js` |
| `no-head-import-in-document` | Prevent usage of `next/head` in `pages/_document.js`. | `pages/_document.js` or `pages/_document/index.js` |
| `no-title-in-document-head` | Prevent usage of `<title>` with `Head` from `next/document`. | Files importing `Head` from `next/document` |
| `no-styled-jsx-in-document` | Prevent usage of `styled-jsx` style tags in `pages/_document.js`. | `pages/_document.js` or `pages/_document/index.js` |
| `no-duplicate-head` | Prevent duplicate usage of `<Head>` in `pages/_document.js`. | `pages/_document.js` class components |
| `no-css-tags` | Prevent manual local stylesheet `<link rel="stylesheet">` tags. | Any JSX element |

Sources: [packages/eslint-plugin-next/src/rules/no-head-element.ts:7-16](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-head-element.ts#L7-L16), [packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts:7-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts#L7-L15), [packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts:7-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts#L7-L15), [packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts:5-14](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts#L5-L14), [packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts:6-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts#L6-L15), [packages/eslint-plugin-next/src/rules/no-duplicate-head.ts:5-14](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-duplicate-head.ts#L5-L14), [packages/eslint-plugin-next/src/rules/no-css-tags.ts:5-13](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-css-tags.ts#L5-L13)

> [!WARNING]
> The `no-head-element` rule checks file paths to determine if code resides within the `app` directory using `path.sep` or `path.posix.sep`, and completely bypasses validation for App Router components.
Sources: [packages/eslint-plugin-next/src/rules/no-head-element.ts:20-28](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-head-element.ts#L20-L28)

### Path Inspection and Document Boundary Enforcement

Rules inspecting `pages/_document.js` parse the filename via Node's `path` module to verify exact matching against `_document` names or `/_document/index` directory structures.

```typescript
const { name, dir } = path.parse(document)

if (
  name.startsWith('_document') ||
  (dir === '/_document' && name === 'index')
) {
```
Sources: [packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts:28-33](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts#L28-L33)

> [!NOTE]
> The `no-css-tags` rule targets JSX opening `link` elements, filtering for attributes where `rel` equals `stylesheet` and `href` is a literal string not matching an absolute protocol via `/^https?/`.
Sources: [packages/eslint-plugin-next/src/rules/no-css-tags.ts:16-37](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-css-tags.ts#L16-L37)

## Font Optimization and External Preconnects

### Google Font Validation Rules

The `eslint-plugin-next` package provides static AST rules to enforce performance best practices when loading Google Fonts via `<link>` elements. These rules inspect JSX opening elements with the name `link` to verify correct attributes, preconnect linkages, and font-display parameters.

| Rule Name | Description | Target URL / Behavior |
| :--- | :--- | :--- |
| `google-font-display` | Enforces `font-display` parameters on Google Font stylesheet links. | `https://nextjs.org/docs/messages/google-font-display` |
| `google-font-preconnect` | Ensures `rel="preconnect"` is present when loading `fonts.gstatic.com`. | `https://nextjs.org/docs/messages/google-font-preconnect` |
| `no-page-custom-font` | Prevents page-only custom Google Font CSS links outside `pages/_document.js`. | `https://nextjs.org/docs/messages/no-page-custom-font` |

Sources: [packages/eslint-plugin-next/src/rules/google-font-display.ts:6-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/google-font-display.ts#L6-L15), [packages/eslint-plugin-next/src/rules/google-font-preconnect.ts:6-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/google-font-preconnect.ts#L6-L15), [packages/eslint-plugin-next/src/rules/no-page-custom-font.ts:12-21](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts#L12-L21)

> [!WARNING]
> The `google-font-display` rule parses the query string of any `https://fonts.googleapis.com/css` link. If the `display` parameter is missing, or if its value is set to `auto`, `block`, or `fallback`, the rule reports a violation.
Sources: [packages/eslint-plugin-next/src/rules/google-font-display.ts:31-50](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/google-font-display.ts#L31-L50)

### Preconnect Inspection and Custom Font Scope

The `google-font-preconnect` rule checks whether `link` tags loading from `https://fonts.gstatic.com` have their `rel` attribute correctly configured with the value `preconnect`. 

```typescript
const hrefValue = attributes.value('href')
const preconnectMissing =
  !attributes.has('rel') ||
  !attributes.hasValue('rel') ||
  attributes.value('rel') !== 'preconnect'

if (
  typeof hrefValue === 'string' &&
  hrefValue.startsWith('https://fonts.gstatic.com') &&
  preconnectMissing
) {
```
Sources: [packages/eslint-plugin-next/src/rules/google-font-preconnect.ts:28-38](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/google-font-preconnect.ts#L28-L38)

> [!NOTE]
> The `no-page-custom-font` rule validates files residing inside the `pages` directory. It checks whether `https://fonts.googleapis.com/css` links are placed within `pages/_document.js` and inside the default export component, reporting a warning if custom fonts are scoped only to individual pages.
Sources: [packages/eslint-plugin-next/src/rules/no-page-custom-font.ts:22-35](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts#L22-L35), [packages/eslint-plugin-next/src/rules/no-page-custom-font.ts:142-167](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts#L142-L167)

## Routing, Navigation, and Image Rules

### Routing and Navigation Rules

The `eslint-plugin-next` package implements static analysis rules to discourage unoptimized markup and raw browser location transitions in favor of framework-managed alternatives. These checks target native JSX elements and imperative window navigation properties.

| Rule Name | Description | Target Documentation URL |
| :--- | :--- | :--- |
| `no-img-element` | Prevent usage of `<img>` element due to slower LCP and higher bandwidth. | `https://nextjs.org/docs/messages/no-img-element` |
| `no-html-link-for-pages` | Prevent usage of `<a>` elements to navigate to internal Next.js pages. | `https://nextjs.org/docs/messages/no-html-link-for-pages` |
| `no-location-assign-relative-destination` | Prevent usage of `location.assign` or `location.href` assignment to navigate to internal Next.js pages. | `https://nextjs.org/docs/messages/no-location-assign-relative-destination` |

Sources: [packages/eslint-plugin-next/src/rules/no-img-element.ts:4-16](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-img-element.ts#L4-L16), [packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts:38-66](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts#L38-L66), [packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts:10-28](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts#L10-L28)

> [!WARNING]
> The `no-img-element` rule checks JSX opening elements named `img`. It ignores elements without attributes, elements wrapped in a parent `<picture>` element, and metadata route files such as `opengraph-image.js`, `twitter-image.js`, or `icon.js` within the App Directory.
Sources: [packages/eslint-plugin-next/src/rules/no-img-element.ts:25-47](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-img-element.ts#L25-L47)

### HTML Link and Location Assignment Checks

The `no-html-link-for-pages` rule verifies all anchor (`<a>`) elements against known routes derived from the filesystem's `pages` and `app` directories. Outgoing absolute links, links with a `download` attribute, and links with `target="_blank"` are automatically exempted from validation.

```typescript
if (target && target.value.value === '_blank') {
  return
}

const href = node.attributes.find(
  (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'href'
)

if (!href || (href.value && href.value.type !== 'Literal')) {
  return
}

const hasDownloadAttr = node.attributes.find(
  (attr) =>
    attr.type === 'JSXAttribute' && attr.name.name === 'download'
)

if (hasDownloadAttr) {
  return
}

const hrefPath = normalizeURL(href.value.value)
// Outgoing links are ignored
if (/^(https?:\/\/|\/\/)/.test(hrefPath)) {
  return
}

```
Sources: [packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts:128-154](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts#L128-L154)

> [!NOTE]
> The `no-location-assign-relative-destination` rule inspects `CallExpression` nodes matching `assign` and `AssignmentExpression` nodes targeting `href` on global identifiers including `window`, `globalThis`, `document`, and `self`. It resolves static string prefixes, template literals, and variable assignments to detect relative URL destinations.
Sources: [packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts:39-80](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts#L39-L80), [packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts:85-86](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts#L85-L86), [packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts:133-169](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-location-assign-relative-destination.ts#L133-L169)

## Component Boundaries and Migration Codemods

### Component Boundary Rules and Internal Checks

The `eslint-plugin-next` package includes static analysis rules to enforce architectural constraints at React component boundaries. Specifically, the `no-async-client-component` rule flags Client Components that are declared as asynchronous functions. It inspects programs for the presence of a `'use client'` directive statement followed by an `export default` declaration or an exported identifier referencing an asynchronous function declaration or an arrow function expression whose name begins with an uppercase letter.

Additionally, internal validation plugins such as `eslint-plugin-internal` provide auxiliary AST checks like `no-ambiguous-jsx`. This rule identifies unbundled JSX fragments (`JSXFragment`) and opening elements (`JSXOpeningElement`), reporting ambiguous usage that would otherwise default to React Client when not bundled in `next-server`.

| Rule / Plugin | Target AST Nodes | Violation Condition |
| :--- | :--- | :--- |
| `no-async-client-component` | `Program`, `ExportDefaultDeclaration`, `FunctionDeclaration`, `VariableDeclaration` | `use client` directive present alongside an `async` default function or variable declaration with a capitalized identifier. |
| `no-ambiguous-jsx` | `JSXOpeningElement`, `JSXFragment` | Unbundled JSX syntax not routed through framework compilation modules for React Server Components. |

Sources: [packages/eslint-plugin-next/src/rules/no-async-client-component.ts:22-108](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-async-client-component.ts#L22-L108), [packages/eslint-plugin-internal/src/eslint-no-ambiguous-jsx.js:19-31](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-internal/src/eslint-no-ambiguous-jsx.js#L19-L31)

> [!WARNING]
> The `no-async-client-component` rule evaluates identifier capitalization via `/[A-Z]/.test(str?.[0])` to distinguish React components from standard helper functions, reporting violations directly on the matching function declaration or variable declarator node.
Sources: [packages/eslint-plugin-next/src/rules/no-async-client-component.ts:7-9](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-async-client-component.ts#L7-L9), [packages/eslint-plugin-next/src/rules/no-async-client-component.ts:76-102](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/eslint-plugin-next/src/rules/no-async-client-component.ts#L76-L102)

### Next Lint Flat Config CLI Migration

The `next-lint-to-eslint-cli` codemod utility transforms legacy ESLint configurations and `FlatCompat` usages into native ESLint v9 flat configuration structures. It checks project roots for existing flat or legacy configuration files, parses configuration syntax with jscodeshift, and extracts extended Next.js rulesets from `compat.extends()` or `compat.config()` calls.

```typescript
function findExistingEslintConfig(projectRoot: string): {
  exists: boolean
  path: string | null
  isLegacy: boolean | null
} {
  const flatConfigs = [
    'eslint.config.js',
    'eslint.config.mjs',
    'eslint.config.cjs',
    'eslint.config.ts',
    'eslint.config.mts',
    'eslint.config.cts',
  ]

  const legacyConfigs = [
    '.eslintrc.js',
    '.eslintrc.cjs',
    '.eslintrc.yaml',
    '.eslintrc.yml',
    '.eslintrc.json',
    '.eslintrc',
  ]

  // Check for flat configs first (preferred for v9+)
  for (const config of flatConfigs) {
    const configPath = path.join(projectRoot, config)
    if (existsSync(configPath)) {
      return { exists: true, path: configPath, isLegacy: false }
    }
  }

  // Check for legacy configs
  for (const config of legacyConfigs) {
    const configPath = path.join(projectRoot, config)
    if (existsSync(configPath)) {
      return { exists: true, path: configPath, isLegacy: true }
    }
  }

  return { exists: false, path: null, isLegacy: null }
}
```
Sources: [packages/next-codemod/transforms/next-lint-to-eslint-cli.ts:66-106](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next-codemod/transforms/next-lint-to-eslint-cli.ts#L66-L106)

> [!NOTE]
> During configuration transformation, the codemod detects project TypeScript capability via `tsconfig.json` existence to select between `ESLINT_CONFIG_TEMPLATE_TYPESCRIPT` and `ESLINT_CONFIG_TEMPLATE_JAVASCRIPT`, automatically inserting standard ignore patterns for `node_modules/**`, `.next/**`, `out/**`, `build/**`, and `next-env.d.ts`.
Sources: [packages/next-codemod/transforms/next-lint-to-eslint-cli.ts:24-64](https://github.com/blade47/next-codemod/transforms/next-lint-to-eslint-cli.ts#L24-L64), [packages/next-codemod/transforms/next-lint-to-eslint-cli.ts:132-164](https://github.com/blade47/next-codemod/transforms/next-lint-to-eslint-cli.ts#L132-L164)

## Related

- [TypeScript Plugin](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/built-in-features/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.
