Skip to content

fix(app-router): discover nested leaf slot routes - #2350

Merged
james-elicx merged 4 commits into
mainfrom
codex/fix-parallel-leaf-children-default
Jun 26, 2026
Merged

james-elicx merged 4 commits into
mainfrom
codex/fix-parallel-leaf-children-default

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • retain parallel slots that only contain nested page routes
  • materialize slot-only leaf routes with the parent children default
  • add focused route-graph and browser regressions

Next.js parity

Fixes the remaining failure from:

test/e2e/app-dir/parallel-routes-leaf-segments/parallel-routes-leaf-segments.no-build-error.test.ts

The original requested parallel-routes-use-selected-layout-segment failures are already covered by #2246. PR #2347 covers the separate parallel-routes-layouts suite.

Reference: https://github.com/vercel/next.js/blob/v16.2.6/test/e2e/app-dir/parallel-routes-leaf-segments/parallel-routes-leaf-segments.no-build-error.test.ts

Validation

  • vp test run tests/app-route-graph.test.ts — 58 passed
  • focused App Router browser regression, one worker — 1 passed
  • exact Next.js v16.2.6 deploy suite, concurrency 1 — 5 passed
  • focused vp check — passed
  • vp run vinext#build — passed

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Jun 25, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2350
npm i https://pkg.pr.new/vinext@2350

commit: c544999

@github-actions

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: /bigbonk — found a blocking regression

The core fix is correct and matches Next.js behavior for the no-children leaf case (verified against the v16.2.6 fixture: /no-children/other renders the slot page with the parent default.tsx as children). But the change is too broad and breaks an existing test that encodes deliberate behavior.

Blocking: breaks tests/routing.test.ts:681

The new !hasNestedPages guard in discoverParallelSlots (packages/vinext/src/routing/app-route-graph.ts:2173) retains the slot unconditionally whenever it contains a nested page — regardless of whether the parent owns a children page. This regresses the pre-existing test:

appRouter - route discovery > does not discover nested @slot sub-routes when the slot root has no page or default (tests/routing.test.ts:681)

AssertionError: expected [ '/inbox', '/inbox/profile' ] to not include '/inbox/profile'
 ❯ tests/routing.test.ts:694:28

That fixture is:

inbox/page.tsx              # parent HAS a children page
inbox/default.tsx           # children default exists
inbox/@modal/profile/page.tsx   # slot has only a nested page, no slot page/default

Before this PR, @modal was dropped by discoverParallelSlots (no page/default/intercept), so /inbox/profile was never minted. After this PR, the slot is retained, discoverSlotSubRoutes sees the parent inbox/default.tsx as childrenFallback, and /inbox/profile is now materialized — the exact opposite of what the test asserts.

Why this matters / Next.js parity

The PR's own comment says the intent is to materialize the leaf slot route "when the owner has no children page of its own" (app-route-graph.ts:2171), but the code never checks that. The distinction is real:

  • PR's new fixture (parallel-leaf-default/): parent has only default.tsx (layout-only, no page.tsx) → Next.js discovers /.../other. ✅ Correct.
  • Line-681 fixture (inbox/): parent has page.tsx + default.tsx → vinext deliberately does not discover /inbox/profile. There is no Next.js test asserting this shape should be discovered, so flipping it silently diverges from a documented, intentional vinext decision.

The Next.js v16.2.6 no-children fixture (the one this PR ports) has the parent with only default.tsx, confirming the leaf behavior should be gated on the parent being layout-only — not applied whenever any nested page exists.

Suggested fix

Narrow the new guard so the slot is only retained for its nested pages when the owner has no children page (i.e. layout-only parent), matching the comment's stated intent. Then re-run tests/routing.test.ts (both line-663 and line-681 cases) in addition to tests/app-route-graph.test.ts, and add a unit test for the line-681 shape to lock in the distinction.

Process note

The PR validation list ran tests/app-route-graph.test.ts but not tests/routing.test.ts / tests/route-sorting.test.ts. Per AGENTS.md, routing changes should run tests/routing.test.ts and tests/route-sorting.test.ts — that would have caught this before CI.


The fixtures, the e2e regression, and the route-graph test additions are all good. Once the guard is scoped to layout-only parents (or the line-681 test is intentionally revised with documented Next.js justification — which I could not find), this is mergeable.

@ask-bonk

ask-bonk Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Posted the review on PR #2350. Summary of findings:

Blocking regression found

The PR's change in discoverParallelSlots (app-route-graph.ts:2173) adds an unconditional !hasNestedPages guard that retains a parallel slot whenever it contains any nested page. This breaks an existing, deliberate test:

tests/routing.test.ts:681 — "does not discover nested @slot sub-routes when the slot root has no page or default"

AssertionError: expected [ '/inbox', '/inbox/profile' ] to not include '/inbox/profile'

Root cause

The PR's comment states the intent is to materialize the leaf route "when the owner has no children page of its own", but the code never checks that condition. The real distinction:

  • PR's fixture (parallel-leaf-default/): parent is layout-only (default.tsx only, no page.tsx) → Next.js v16.2.6 discovers the route ✅
  • Line-681 fixture (inbox/): parent HAS page.tsx + default.tsx → vinext intentionally does NOT discover /inbox/profile, and the PR now flips that silently.

I verified the authoritative Next.js behavior: the v16.2.6 no-children fixture (the one ported) has a parent with only default.tsx, confirming the guard should be scoped to layout-only parents, not applied broadly.

Recommended fix

Narrow the guard to layout-only parents (matching the comment's stated intent), re-run tests/routing.test.ts and tests/route-sorting.test.ts (which AGENTS.md requires for routing changes — the PR only ran tests/app-route-graph.test.ts), and add a unit test locking in the line-681 distinction.

The fixtures, e2e regression, and route-graph test additions themselves are all correct and well-referenced.

github run

@github-actions

github-actions Bot commented Jun 25, 2026 •

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared c544999 against base de014d4 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 117.1 KB 117.1 KB ⚫ +0.0%
Client entry size (gzip) vinext 112.5 KB 112.5 KB ⚫ +0.0%
Dev server cold start vinext 2.52 s 2.55 s ⚫ +1.2%
Production build time vinext 3.00 s 3.01 s ⚫ +0.4%
RSC entry closure size (gzip) vinext 93.0 KB 93.0 KB ⚫ -0.0%
Server bundle size (gzip) vinext 157.0 KB 156.9 KB ⚫ -0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk

ask-bonk Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: /bigbonk — LGTM, prior blocking regression is resolved

The earlier /bigbonk pass flagged a blocking regression: the original !hasNestedPages guard in discoverParallelSlots was unconditional and broke tests/routing.test.ts:681 ("does not discover nested @slot sub-routes when the slot root has no page or default"). Commit c544999 (fix(app-router): scope nested leaf slot discovery) fixes this correctly.

What the fix does

The guard is now gated behind a new opt-in includeNestedOnlySlots parameter (default false), threaded through directoryToAppRoute → discoverInheritedParallelSlots → discoverParallelSlots:

  • app-route-graph.ts:2182 — hasNestedPages only counts when includeNestedOnlySlots is set.
  • app-route-graph.ts:1903 — at the inherited-slot level it is further narrowed to includeNestedOnlySlots && isActiveUrlLevel, so ancestor/inherited slots are never retained for nested pages alone.
  • app-route-graph.ts:960,962 — the flag is passed true only from the ghost-parent loop, which scans **/layout and therefore only fires for layout-bearing parents.

This is exactly the "layout-only parent" scoping the previous review asked for. The two fixtures are now correctly distinguished:

  • Layout-only parent (inbox/layout.tsx + default.tsx, no page.tsx) → /inbox/profile is discovered (tests/routing.test.ts:702). Matches Next.js v16.2.6 no-children.
  • Page-bearing parent (inbox/page.tsx + default.tsx) → /inbox/profile is not discovered (tests/routing.test.ts:681). The inbox route is minted via the normal page path with includeNestedOnlySlots=false, so @modal is dropped and discoverSlotSubRoutes skips it (no slots). The ghost loop never touches it (no layout.tsx).

Verification (ran the AGENTS.md-required routing suites the original PR validation skipped)

  • vp test run tests/routing.test.ts — 113 passed (includes both line-681 and line-702 cases)
  • vp test run tests/route-sorting.test.ts tests/app-route-graph.test.ts — 105 passed
  • vp check packages/vinext/src/routing/app-route-graph.ts — format, lint, types clean

Notes

  • Fixtures, the e2e regression (advanced.spec.ts), and the route-graph unit tests are all correct and reference the authoritative Next.js v16.2.6 source.
  • The includeNestedOnlySlots && isActiveUrlLevel narrowing also correctly avoids retaining nested-only slots for inherited ancestor slots, which prevents over-discovery on deeper routes.

No blocking issues found. This is mergeable.

@ask-bonk

ask-bonk Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Posted the review on PR #2350.

Summary

The previous /bigbonk review flagged a blocking regression where the unconditional !hasNestedPages guard broke tests/routing.test.ts:681. A subsequent commit (c544999 fix(app-router): scope nested leaf slot discovery) resolves it correctly.

The fix gates the guard behind a new opt-in includeNestedOnlySlots parameter (default false), passed true only from the ghost-parent loop, which scans **/layout and therefore only fires for layout-bearing parents. It's further narrowed to includeNestedOnlySlots && isActiveUrlLevel so inherited ancestor slots are never retained for nested pages alone.

This achieves exactly the "layout-only parent" scoping the prior review requested:

  • Layout-only parent → /inbox/profile discovered (matches Next.js v16.2.6 no-children)
  • Page-bearing parent → /inbox/profile not discovered (intentional vinext behavior preserved)

I verified by running the routing suites the original PR validation skipped (per AGENTS.md):

  • tests/routing.test.ts — 113 passed
  • tests/route-sorting.test.ts + tests/app-route-graph.test.ts — 105 passed
  • vp check on the modified file — clean

No blocking issues found. Fixtures, e2e regression, and unit tests are all correct and reference the authoritative Next.js source.

github run

@james-elicx
james-elicx marked this pull request as ready for review June 25, 2026 16:56
@james-elicx
james-elicx merged commit a21667f into main Jun 26, 2026
58 checks passed
@james-elicx
james-elicx deleted the codex/fix-parallel-leaf-children-default branch June 26, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant