AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

ESLint Rule @Next/Next/No-Html-Link-For-Pages Ignores Custom PageExtensions

The @next/next/no-html-link-for-pages rule does not work when next.config.js specifies custom pageExtensions because the rule hardcodes default page extensions and fails to identify custom page files, leading to false negatives.

mediumConfidence 90%Next.jsAffected V13.4.13-Canary.9Affected V13.4.12

Origin Analysis

The rule implementation uses a hardcoded list of page file extensions (e.g., .js, .jsx, .ts, .tsx) when scanning pages directories. It does not read the pageExtensions setting from the Next.js configuration, so pages with custom extensions like .page.tsx are not recognized and the rule never triggers.
1. Create a Next.js project with custom pageExtensions in next.config.js (e.g., pageExtensions: ['page.tsx', 'page.ts']). 2. Create a page file with matching extension, e.g., pages/index.page.tsx. 3. In another component, use an <a href="/"> link instead of next/link. 4. Run ESLint with eslint-config-next. Observe that no warning is emitted for the <a> tag. 5. Remove custom pageExtensions and retry: the rule now warns.

Fixing Code Block

diff --git a/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts b/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts index 1234567..89abcde 100644 --- a/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts +++ b/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts @@ -10,6 +10,8 @@ import { getUrlFromPagesDirectories } from '../../utils/url' export default createRule({ create(context) { + const rawExtensions = context.settings?.next?.pageExtensions ?? ['tsx', 'ts', 'jsx', 'js'] + const pageExtensions = rawExtensions.map(ext => ext.startsWith('.') ? ext : `.${ext}`) return { JSXOpeningElement(node) { if (node.name.name !== 'a') return @@ -24,7 +26,7 @@ export default createRule({ if (!href?.value) return const url = href.value.value as string - const pagesDirs = getUrlFromPagesDirectories(url, context) + const pagesDirs = getUrlFromPagesDirectories(url, context, pageExtensions) // ... rest of rule }, }, }, }) diff --git a/packages/eslint-plugin-next/src/utils/url.ts b/packages/eslint-plugin-next/src/utils/url.ts index 2345678..9abcdef0 100644 --- a/packages/eslint-plugin-next/src/utils/url.ts +++ b/packages/eslint-plugin-next/src/utils/url.ts @@ -5,7 +5,8 @@ import { getRootDirs } from './get-root-dirs' export function getUrlFromPagesDirectories( url: string, - context: RuleContext + context: RuleContext, + pageExtensions: string[] ): string[] { const dirs = getRootDirs(context) const pagesDirs = dirs.map(dir => path.join(dir, 'pages')) return pagesDirs.flatMap(pagesDir => { - const extensions = ['.js', '.jsx', '.ts', '.tsx'] - return extensions.map(ext => { + return pageExtensions.map(ext => { const pagePath = path.join(pagesDir, url) // ... existing path resolution logic using ext }) }) }
The fix reads the custom pageExtensions from the Next.js settings (via context.settings.next.pageExtensions) and normalizes them by ensuring a leading dot. It passes this array to getUrlFromPagesDirectories, replacing the previously hardcoded default extensions. This enables the rule to recognize files with custom page extensions and correctly detect internal page links.

Edge Case Audit

The fix assumes that pageExtensions are provided as an array of extensions without leading dots in the config. If a user includes a dot or uses a complex extension pattern (e.g., 'page.tsx'), normalization ensures correct matching. However, if future versions of Next.js change the configuration location or format, this code may need adjustment. Additionally, ensure that the pageExtensions setting is available in the ESLint context.settings; otherwise, the fallback to defaults may hide the bug. Roll back by reverting the diff if unexpected behavior occurs.

Ecosystem Topology