Font preload tags not emitted when using multi-dot pageExtensions (e.g. 'page.tsx')
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.4k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://github.com/KinjiKawaguchi/nextjs-font-preload-multi-dot-repro
To Reproduce
- Clone the reproduction repository and install (
pnpm install). - Build and start (
pnpm build && pnpm start). curl -s http://localhost:3000/ | grep 'rel="preload".*font'- Nothing prints — no
<link rel="preload" as="font">tag is present in the HTML. - To verify the working case, check out the
controlbranch (git checkout control) and repeat steps 2 and 3. Thecontrolbranch removes thepageExtensionsconfig and renames the files back tolayout.tsx/page.tsx. Font preload tags now appear in the HTML.
Current vs. Expected behavior
Current behavior
With pageExtensions: ['page.tsx'], <link rel="preload" as="font"> tags are not emitted for fonts imported via next/font on any statically prerendered route. CSS and JS preload tags for the same route are still emitted correctly, so the omission is specific to fonts. The browser only discovers the font URLs after parsing CSS @font-face rules, which delays LCP for any text using the font.
Expected behavior
Font preload tags should be emitted regardless of the pageExtensions value. pageExtensions is a single top-level next.config.js option that applies to both the app and pages directories, and the pageExtensions docs list this exact multi-dot pattern as supported:
Then, rename your pages to have a file extension that includes
.page(e.g. renameMyPage.tsxtoMyPage.page.tsx).module.exports = { pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'], }
The issue does not reproduce when
pageExtensionsis left at its default (font preload tags are emitted normally).pageExtensionsis set to a single-extension value like['tsx'].- The same file uses a single-dot extension (
layout.tsx→ font preload OK;layout.page.tsx→ font preload missing).
CSS and JS preload continue to work in every case. The omission is specific to next/font.
Provide environment information
Operating System:
Platform: linux
Arch: x64
Version: #85-Ubuntu SMP PREEMPT_DYNAMIC Thu Sep 18 15:26:59 UTC 2025
Available memory (MB): 1547759
Available CPU cores: 32
Binaries:
Node: 24.15.0
npm: 11.12.1
Yarn: N/A
pnpm: 10.33.0
Relevant Packages:
next: 16.3.0-canary.76 // Latest available version is detected (16.3.0-canary.76).
eslint-config-next: N/A
react: 19.2.7
react-dom: 19.2.7
typescript: 5.9.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Font (next/font), Webpack, Turbopack
Which stage(s) are affected? (Select all that apply)
next build (local), next start (local), Vercel (Deployed)
Additional context
Digging into the source, the two internal manifests emit keys in different formats for the same source file. entryCSSFiles / entryJSFiles (from flight-manifest-plugin) produce keys like [project]/app/layout.page, where only the final .tsx has been stripped from layout.page.tsx. next-font-manifest.app (from next-font-manifest-plugin) produces keys like [project]/app/page, where all extensions have been stripped so .page is gone.
At runtime both the CSS/JS preload and font preload paths normalize the source path with the same regex.
filePath.replace(/\.[^.]+$/, '')
That strips exactly one extension (.tsx), producing layout.page / page.page. CSS/JS lookups succeed because the manifest key layout.page matches. Font lookups fail because the manifest key page does not match page.page, so getPreloadableFonts returns null and no preload tag is emitted.
The relevant files (paths from packages/next/src) are server/app-render/get-preloadable-fonts.tsx (font lookup regex), server/app-render/get-css-inlined-link-tags.tsx (CSS/JS lookup with the same regex), build/webpack/plugins/next-font-manifest-plugin.ts (font manifest key), and build/webpack/plugins/flight-manifest-plugin.ts (CSS/JS manifest key).
Aligning the font manifest key format with the CSS/JS manifest key format (i.e. retain the pre-.tsx stem) looks like the smallest diff. I have not verified this in a PR yet; happy to send one if maintainers agree with the direction.
Related issues
I searched existing issues for getPreloadableFonts, pageExtensions font preload, next-font-manifest, and multi-dot pageExtensions, and did not find a direct duplicate.
#62332 ("Font preload not working as expected") has the same symptom of font preload tags missing, but none of the sub-reports there involve pageExtensions. Reported conditions include Windows-specific behavior, a VERCEL_CLI_VERSION regression around 33.5.1, a next dev --turbo regression in 15.4.4, and next/font/local on default pageExtensions. The reproduction in this issue is independent of all of those. It happens on Linux, on next start without the Vercel CLI, without Turbopack in dev, using next/font/google, and cleanly toggles on and off with the pageExtensions value. If maintainers consider this a facet of #62332 rather than a distinct bug, feel free to close and I will move the analysis there as a comment.
#53473 / #91746 report the no-html-link-for-pages ESLint rule not respecting custom pageExtensions. Different feature area, but the same underlying pattern of a Next.js internal hardcoding single-dot extension assumptions. Flagging in case there is a shared fix opportunity.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Run the linked reproduction with pnpm install, pnpm build && pnpm start, and the curl check first. Then read server/app-render/get-preloadable-fonts.tsx, get-css-inlined-link-tags.tsx, and the next-font-manifest-plugin.ts and flight-manifest-plugin.ts files to compare manifest keys. Done means font preload tags appear with multi-dot pageExtensions without regressing CSS or JS preloads.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, react, typescript, webpack
- Domain
- build-system, frontend, performance
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 62/100