Architecture Overview
Server Runtime
Rendering Pipeline
Client Navigation
Caching and Export
Development Tools
Build and Configuration
Ecosystem Packages
Testing Infrastructure
How It Works
The following files were used as context for generating this wiki page:
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).
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.
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.
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).
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.
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, packages/eslint-plugin-next/src/rules/google-font-preconnect.ts:18-45
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.
Note
google-font-display recommends appending &display=optional to Google Font URLs to prevent flash of invisible text (FOIT) and layout shifts.
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.
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.
// Example triggering no-img-element
export default function Profile() {
return <img src="/profile.png" alt="Profile" />
}Script loading is strictly governed by rules preventing synchronous blocking scripts, missing script identifiers, and misconfigured third-party integrations.
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, packages/eslint-plugin-next/src/rules/inline-script-id.ts:5-75
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/index.ts:26-56, packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts:76-144
Custom document files (pages/_document.js or pages/_document.tsx) have rigid architectural requirements. The plugin enforces these through specialized boundary rules.
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, packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts:16-42, packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts:15-55
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, packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts:6-48, packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts:10-72
Caution
Importing next/document in a regular page component breaks server-side rendering execution order and markup injection.
To encourage client-side routing performance and prevent full-page reloads, rules target navigation mechanisms.
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.
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.
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.
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.
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, packages/eslint-plugin-next/src/rules/no-typos.ts:53-109
// Example triggering no-typos error
export async function getStaticProp() {
return { props: { data: null } }
}