Fix catch-all index page being served for every other slug - #96553
Merged
Conversation
Contributor
Tests PassedCommit: ca66f87 |
Contributor
Stats from current PR🔴 1 regression
📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles
Server Middleware
Build DetailsBuild Manifests
Build Cache
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📝 Changed Files (10 files)Files with changes:
View diffsapp-page-exp..ntime.dev.jsfailed to diffapp-page-exp..time.prod.jsDiff too large to display app-page-tur..ntime.dev.jsfailed to diffapp-page-tur..time.prod.jsDiff too large to display app-page-tur..ntime.dev.jsfailed to diffapp-page-tur..time.prod.jsDiff too large to display app-page.runtime.dev.jsfailed to diffapp-page.runtime.prod.jsDiff too large to display pages-api.runtime.dev.jsDiff too large to display pages.runtime.dev.jsDiff too large to display 📎 Tarball URLCommit: ca66f87 |
acdlite
force-pushed
the
worktree-segment-cache-vary-params-authoritative
branch
from
August 3, 2026 20:33
b446697 to
5510993
Compare
acdlite
force-pushed
the
worktree-segment-cache-vary-params-authoritative
branch
from
August 3, 2026 20:49
5510993 to
1828bb2
Compare
acdlite
marked this pull request as ready for review
August 3, 2026 20:52
gnoff
approved these changes
Aug 3, 2026
acdlite
force-pushed
the
worktree-segment-cache-vary-params-authoritative
branch
from
August 3, 2026 20:57
1828bb2 to
4bd909d
Compare
ztanner
approved these changes
Aug 3, 2026
Navigating between sibling pages of an optional catch-all could leave the URL updated but the content stuck on the route's index page, with no request made at all. On nextjs.org that is every /docs/* sidebar link, once /docs itself has been prefetched. The App Shell of a segment is the part that doesn't depend on params, so the cache keeps one copy and reuses it for every param value. The client decides it has that shell when a response arrives without a shell/full split. But a response arrives without a split whenever the render had no dynamic hole, and that is also what a page fully prerendered at concrete params looks like. The split alone can't tell the two apart, and the second is not param-independent at all — its content is the params. So the /docs page was stored as the shell for every slug. Because a payload that needs nothing further is also recorded complete, sibling slugs read it and stopped there: no prefetch, and no dynamic request during navigation to correct it. Treat a payload as param-independent only on evidence. The server already reports which params each segment depends on, so use that. Without it, assume every param varies, except for a shell variant, which reduces param-dependent content to param fallbacks and so is good for any value of them. The new e2e suite prefetches a fully static catch-all index, navigates there, then prefetches and navigates to a sibling slug. It fails on canary with a prefetch that never fires, and is verified with vary params both enabled and disabled.
acdlite
force-pushed
the
worktree-segment-cache-vary-params-authoritative
branch
from
August 3, 2026 21:11
4bd909d to
ca66f87
Compare
acdlite
enabled auto-merge (squash)
August 3, 2026 21:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Navigating between sibling pages of an optional catch-all could leave the URL updated but the content stuck on the route's index page, with no request made at all. On nextjs.org that is every /docs/* sidebar link, once /docs itself has been prefetched.
The App Shell of a segment is the part that doesn't depend on params, so the cache keeps one copy and reuses it for every param value. The client decides it has that shell when a response arrives without a shell/full split.
But a response arrives without a split whenever the render had no dynamic hole, and that is also what a page fully prerendered at concrete params looks like. The split alone can't tell the two apart, and the second is not param-independent at all — its content is the params. So the /docs page was stored as the shell for every slug. Because a payload that needs nothing further is also recorded complete, sibling slugs read it and stopped there: no prefetch, and no dynamic request during navigation to correct it.
Treat a payload as param-independent only on evidence. The server already reports which params each segment depends on, so use that. Without it, assume every param varies, except for a shell variant, which reduces param-dependent content to param fallbacks and so is good for any value of them.
The new e2e suite prefetches a fully static catch-all index, navigates there, then prefetches and navigates to a sibling slug. It fails on canary with a prefetch that never fires, and is verified with vary params both enabled and disabled.