Dynamic `sitemap.ts` under a dynamic segment with `generateStaticParams` is collapsed to `/-/sitemap.xml` — per-param sitemaps no longer emitted (regression 16.2.3 → 16.3)
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/Debbl/next-dynamic-sitemap-repro
To Reproduce
- Clone the reproduction repository and check out the plain repro commit — note that
mainHEAD additionally contains a pnpm patch (patches/next@16.3.4.patch) demonstrating the proposed fix, so buildingmainas-is does NOT reproduce the bug:git clone https://github.com/Debbl/next-dynamic-sitemap-repro cd next-dynamic-sitemap-repro git checkout d2e42032449c7f92113ba0e591b2979f4fd1de55 # plain repro, no patch pnpm installpnpm build(runsnext buildwithoutput: 'export')find out -name 'sitemap*'
The app has a dynamic [lang] segment with a code-based sitemap:
app/
layout.js
[lang]/
page.js # generateStaticParams -> en, zh
sitemap.js # generateStaticParams -> en, zh; export const dynamic = 'force-static'
Current vs. Expected behavior
Current behavior (next 16.3.4)
The sitemap route is collapsed to a single --placeholder pathname. generateStaticParams of sitemap.js is never invoked:
└ /-/sitemap.xml
└ ● /-/sitemap.xml
Only out/-/sitemap.xml is emitted. out/en/sitemap.xml and out/zh/sitemap.xml do not exist, so any robots.txt pointing at /{lang}/sitemap.xml (the URLs previous versions produced, which search consoles have indexed) now 404s.
The same happens without output: 'export': prerender-manifest.json routes contains only /-/sitemap.xml, no /en/sitemap.xml / /zh/sitemap.xml entries.
Expected behavior (next 16.2.3)
└ ● /[lang]/sitemap.xml
├ /en/sitemap.xml
└ /zh/sitemap.xml
out/en/sitemap.xml and out/zh/sitemap.xml are emitted (verify with pnpm add next@16.2.3 && rm -rf .next out && pnpm build).
Root cause analysis
This looks like fallout from #93873 ("Prerender static metadata under dynamic segments to canonical pathname"). That optimization is meant for static metadata files (e.g. /[id]/apple-icon.png), whose bytes don't depend on params. But the check matches by pathname shape only:
- In
isPageStatic(packages/next/src/build/utils.ts),isStaticMetadataFile(pathname)matches/[lang]/sitemap.xmland takes thebuildStaticMetadataStaticPathsshortcut, skippingbuildAppStaticPathsentirely — so the userlandgenerateStaticParamsexported bysitemap.js(which the metadata route loader re-exports viacreateReExportsCode) is never called. - One-liner showing the collapse:
node -e "const{getStaticMetadataPrerenderPathname:f}=require('next/dist/lib/metadata/get-metadata-route.js');console.log(f('/[lang]/sitemap.xml'))" # → /-/sitemap.xml
A guard on the shortcut fixes it while keeping the #93873 optimization for true static files (static metadata files never export generateStaticParams — getStaticAssetRouteCode emits none):
if (
routeModule.definition.kind === RouteKind.APP_ROUTE &&
isStaticMetadataFile(pathname) &&
!segments.some((segment) => typeof segment.generateStaticParams === 'function')
) {
Verified locally against the repro: per-param sitemaps are restored, and a true static file (app/[lang]/icon.png) still collapses to a single out/-/icon.png.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin 25.5.0
Binaries:
Node: 24.13.0
npm: 11.6.2
Yarn: N/A
pnpm: 10.26.0
Relevant Packages:
next: 16.3.4
eslint-config-next: N/A
react: 19.2.8
react-dom: 19.2.8
typescript: N/A
Next.js Config:
output: export
Which area(s) are affected? (Select all that apply)
Metadata, Dynamic Routes, Output
Which stage(s) are affected? (Select all that apply)
next build (local)
Additional context
- Regression window: 16.2.3 works, 16.3.4 does not (introduced by #93873, first shipped in the 16.3 line).
- Reproduces locally with both
next build+output: 'export'and a plain server build (the export just makes the missing files obvious; in a server build the collapsed route lands inprerenderManifest.routesas/-/sitemap.xmlthe same way). - Real-world impact: localized sitemaps under
app/[lang]/sitemap.tsdisappear from the build output, breakingrobots.txtSitemap:entries and search-console registrations that point at/{lang}/sitemap.xml. - Repro repo layout: commit
d2e4203is the plain reproduction;mainHEAD (831e964) additionally applies the proposed fix as a pnpm patch (patches/next@16.3.4.patch) — buildingmainshows the expected per-param output (out/en/sitemap.xml,out/zh/sitemap.xml) while a true static file (app/[lang]/icon.png) still collapses to a singleout/-/icon.png, i.e. the #93873 optimization is preserved. - The repro README contains the 16.2.3 comparison steps and the root-cause notes; happy to open a PR with the guard described above.
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
Start with packages/next/src/build/utils.ts, the isPageStatic path, and getStaticMetadataPrerenderPathname; use the plain repro commit d2e4203 and run pnpm install, pnpm build, and find out -name 'sitemap*'. Done means per-param sitemap files appear for en and zh, while the static icon case remains collapsed; compare with the existing 16.2.3 output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, next.js
- Domain
- build-system, web-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100