Prune incomplete parallel route matchers - #97108
Conversation
Stats from current PR🔴 3 regressions, 2 improvements
📊 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 (1 file)Files with changes:
View diffsserver.runtime.prod.jsDiff too large to display 📎 Tarball URLCommit: 9bd01fa |
Failing test suitesCommit: 9bd01fa | About building and testing Next.js
Expand output● strict-route-matching-known-limitations › metadata error delivery › documents that metadata errors are swallowed on initial load ● strict-route-matching-known-limitations › metadata error delivery › documents that a metadata redirect is swallowed only on initial load Other failing CI jobs |
db528b7 to
9228c2f
Compare
| const nextConfig = {} | ||
| const nextConfig = { | ||
| experimental: { | ||
| pruneUnmatchedParallelRoutes: true, |
There was a problem hiding this comment.
Did you intend to update this test?
There was a problem hiding this comment.
yeah because the unflagged behavior (should be) a superset of the flagged behavior so it is asserting that interception routes still work with the default null behavior that exists when parallel slots do not have children during soft nav. Though it does suggest the main test suite actually should get this coverage too
There was a problem hiding this comment.
Yeah, or maybe we enable it in the existing matrix that we have that turns other flags on? Then coverage is broad across the different parallel route/interception tests. I think that's how we handled the new scroll handler, too.
| @@ -0,0 +1,163 @@ | |||
| import { nextTestSetup } from 'e2e-utils' | |||
|
|
|||
| describe('parallel-routes-pruned-matchers', () => { | |||
There was a problem hiding this comment.
should we assert that a client nav 404s in addition to hard refresh?
d279411 to
8e963bd
Compare
65edfc5 to
cf7c828
Compare
cf7c828 to
560ca01
Compare
560ca01 to
6eda652
Compare
03769c6 to
6403e32
Compare
6403e32 to
10129ba
Compare
9c8393c to
e3a9ee5
Compare
This extends experimental.strictRouteMatching so a matcher is left out when its loader tree contains a synthesized default that will always call notFound(). More specific matchers that combine the catchall with pages in other slots are still included, and adding a default.tsx keeps the broader matcher. The main goal is to make children and named slots behave the same way. If a URL cannot construct a complete route tree on its own, it should be treated as an unmatched URL instead of sometimes showing a slot 404 and sometimes producing a missing page or default error. We got here incrementally. [#47872](#47872) introduced the 404 fallback for unmatched parallel slots, [#60186](#60186) added a development warning because this was confusing in practice and linked [#51805](#51805) and [#49569](#49569), and [#84702](#84702) later made a missing default a build error for named slots while leaving children on the old fallback for backwards compatibility. Strict matching takes the next step and treats a matcher that can only construct a permanent 404 tree as unmatched. This changes soft navigations that only worked by preserving a previously active slot even though the URL could not be loaded directly, which is why it is part of the experimental strict matching behavior. Interception routes that use the null fallback added in [#85779](#85779) are left alone. This change however is well motivated because if you did client nav to a route that only matches a named slot and then hard refresh you will end up getting a 404. This is a sign our current semantics are actually broken. If you want to preserve the perma 404 behavior of the slot-without-default you can just add a default and make it call notFound() unconditionally.
Parallel route layouts do not have to render children. A layout can be composed entirely from named slots, in which case the synthesized children not-found fallback is never rendered and should not make the route incomplete. This tracks whether children is actually declared by a page, default, or ordinary descendant before considering its fallback during pruning. It keeps concrete named-only matches while still pruning broader catchalls when a declared named slot or children branch does not match.
e3a9ee5 to
9bd01fa
Compare
This extends
experimental.strictRouteMatchingso a matcher is left out when its loader tree contains a synthesized default that will always callnotFound(). More specific matchers that combine the catchall with pages in other slots are still included, and adding adefault.tsxkeeps the broader matcher.The main goal is to make children and named slots behave the same way. If a URL cannot construct a complete route tree on its own, it should be treated as an unmatched URL instead of sometimes showing a slot 404 and sometimes producing a missing page or default error.
We got here incrementally. #47872 introduced the 404 fallback for unmatched parallel slots, #60186 added a development warning because this was confusing in practice and linked #51805 and #49569, and #84702 later made a missing default a build error for named slots while leaving children on the old fallback for backwards compatibility. Strict matching takes the next step and treats a matcher that can only construct a permanent 404 tree as unmatched.
This changes soft navigations that only worked by preserving a previously active slot even though the URL could not be loaded directly, which is why it is part of the experimental strict matching behavior. Interception routes that use the null fallback added in #85779 are left alone. This change however is well motivated because if you did client nav to a route that only matches a named slot and then hard refresh you will end up getting a 404. This is a sign our current semantics are actually broken.
If you want to preserve the perma 404 behavior of the slot-without-default you can just add a default and make it call
notFound()unconditionally.The base PR defaults
strictRouteMatchingto true so canary applications and the full CI suite exercise the corrected semantics without requiring opt in. An explicit false keeps the legacy behavior as a temporary escape hatch while this bakes. This is intended to be an opt-out rollout rather than a permanent split in router semantics.