[PR #1051] [MERGED] fix(routing): discover nested parallel slot sub-routes from layout-only parents #1053

Closed
opened 2026-05-06 13:11:45 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/1051
Author: @NathanDrake2406
Created: 5/4/2026
Status: Merged
Merged: 5/5/2026
Merged by: @james-elicx

Base: mainHead: fix/nested-parallel-routes-no-parent-page


📝 Commits (6)

  • 9cebf1e fix(routing): discover nested parallel slot sub-routes from layout-only parents
  • d1fc365 fix(routing): correct patternsStructurallyEquivalent tree-node comparison
  • bfaf811 fix(routing): scan synthetic routes in structural conflict check
  • d12aecb test(routing): cover structural conflict skip for synthetic parallel routes
  • c3a83f9 fix(routing): guard discoverSlotSubRoutes to layout-only UI routes
  • 70d9a19 fix(routing): scan synthetic routes in structural conflict check + regression test

📊 Changes

8 files changed (+208 additions, -4 deletions)

View changed files

📝 packages/vinext/src/routing/app-route-graph.ts (+49 -4)
📝 tests/app-route-graph.test.ts (+76 -0)
📝 tests/app-router.test.ts (+15 -0)
tests/fixtures/app-basic/app/parallel-nested/home/@parallelB/default.tsx (+3 -0)
tests/fixtures/app-basic/app/parallel-nested/home/@parallelB/nested/page.tsx (+8 -0)
tests/fixtures/app-basic/app/parallel-nested/home/layout.tsx (+8 -0)
tests/fixtures/app-basic/app/parallel-nested/layout.tsx (+8 -0)
📝 tests/routing.test.ts (+41 -0)

📄 Description

What this changes

Nested parallel routes now work when the parent segment has a layout but no page.tsx. Previously, discoverSlotSubRoutes in app-route-graph.ts skipped any parent route without a pagePath, so parallel slot sub-pages like @parallelB/nested/page.tsx under home/layout.tsx (with no home/page.tsx) were never discovered as valid URL routes.

This is a high-value App Router parity fix. Next.js explicitly tests this shape in parallel-routes-and-interception:

  • app/parallel-nested/home/layout.tsx
  • app/parallel-nested/home/@parallelB/default.tsx
  • app/parallel-nested/home/@parallelB/nested/page.tsx

...creates a valid /parallel-nested/home/nested route even though there is no home/page.tsx.

Approach

The fix makes four targeted changes to discoverSlotSubRoutes:

  1. Remove the hard pagePath requirement. Layout-only routes with parallel slots can still own nested slot sub-routes.

  2. Compute parentPageDir from the innermost layout when pagePath is null. This keeps slot ownership checks working correctly for layout-only routes.

  3. Relax the childrenDefault (default.tsx) requirement, but only for layout-only parent routes. When a parent HAS a children page, default.tsx is still required as a fallback for the synthetic sub-route. Layout-only parents do not need this fallback because the children slot was never occupied at the parent level.

  4. Add structural-conflict detection before creating synthetic routes. This prevents layout-only root routes from generating synthetic routes that collide with existing page routes differing only by param name (e.g. /shop/:id vs /shop/:name), which validateRoutePatterns rejects. The slot content for the existing route is handled by findMirroredSlotPage instead.

Validation

  • Added unit test in tests/routing.test.ts: "discovers nested parallel slot sub-routes from layout-only parent" (ported from Next.js test suite)
  • Added integration test in tests/app-router.test.ts: "renders nested parallel route from layout-only parent"
  • Added fixture files under tests/fixtures/app-basic/app/parallel-nested/ matching the Next.js reference fixture
  • All 419 routing-related tests pass:
    • tests/routing.test.ts (106 tests)
    • tests/app-route-graph.test.ts (11 tests)
    • tests/app-router.test.ts (301 tests)
    • tests/intercepting-routes-build.test.ts (1 test)
  • Lint passes with 0 warnings/errors

Risks / follow-ups

  • The structural-conflict check uses pattern-part comparison. In theory, two routes could have the same shape but different param names and both be legitimate (e.g. one from a slot, one from a page). Next.js rejects this at build time, so skipping the synthetic route is the safe choice.
  • This fix only affects route discovery. Render-time slot resolution via findMirroredSlotPage is unchanged.

References


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/1051 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 5/4/2026 **Status:** ✅ Merged **Merged:** 5/5/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/nested-parallel-routes-no-parent-page` --- ### 📝 Commits (6) - [`9cebf1e`](https://github.com/cloudflare/vinext/commit/9cebf1eb92bbc126061eb29e0dd86cc3bce99b91) fix(routing): discover nested parallel slot sub-routes from layout-only parents - [`d1fc365`](https://github.com/cloudflare/vinext/commit/d1fc36500fc2d849ee01dad4be6a46ce9c106808) fix(routing): correct patternsStructurallyEquivalent tree-node comparison - [`bfaf811`](https://github.com/cloudflare/vinext/commit/bfaf8113c8bce7ef8c91ca058ff7787b631c9a2c) fix(routing): scan synthetic routes in structural conflict check - [`d12aecb`](https://github.com/cloudflare/vinext/commit/d12aecb591421bfc6e0051f13670b4c5bf1f30b1) test(routing): cover structural conflict skip for synthetic parallel routes - [`c3a83f9`](https://github.com/cloudflare/vinext/commit/c3a83f9257760e1213cf5faf69770465dae67f50) fix(routing): guard discoverSlotSubRoutes to layout-only UI routes - [`70d9a19`](https://github.com/cloudflare/vinext/commit/70d9a19a2d67b47e5a39ff6aa20fddd093882b24) fix(routing): scan synthetic routes in structural conflict check + regression test ### 📊 Changes **8 files changed** (+208 additions, -4 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/routing/app-route-graph.ts` (+49 -4) 📝 `tests/app-route-graph.test.ts` (+76 -0) 📝 `tests/app-router.test.ts` (+15 -0) ➕ `tests/fixtures/app-basic/app/parallel-nested/home/@parallelB/default.tsx` (+3 -0) ➕ `tests/fixtures/app-basic/app/parallel-nested/home/@parallelB/nested/page.tsx` (+8 -0) ➕ `tests/fixtures/app-basic/app/parallel-nested/home/layout.tsx` (+8 -0) ➕ `tests/fixtures/app-basic/app/parallel-nested/layout.tsx` (+8 -0) 📝 `tests/routing.test.ts` (+41 -0) </details> ### 📄 Description ## What this changes Nested parallel routes now work when the parent segment has a layout but no `page.tsx`. Previously, `discoverSlotSubRoutes` in `app-route-graph.ts` skipped any parent route without a `pagePath`, so parallel slot sub-pages like `@parallelB/nested/page.tsx` under `home/layout.tsx` (with no `home/page.tsx`) were never discovered as valid URL routes. This is a high-value App Router parity fix. Next.js explicitly tests this shape in `parallel-routes-and-interception`: - `app/parallel-nested/home/layout.tsx` - `app/parallel-nested/home/@parallelB/default.tsx` - `app/parallel-nested/home/@parallelB/nested/page.tsx` ...creates a valid `/parallel-nested/home/nested` route even though there is no `home/page.tsx`. ## Approach The fix makes four targeted changes to `discoverSlotSubRoutes`: 1. **Remove the hard `pagePath` requirement.** Layout-only routes with parallel slots can still own nested slot sub-routes. 2. **Compute `parentPageDir` from the innermost layout when `pagePath` is null.** This keeps slot ownership checks working correctly for layout-only routes. 3. **Relax the `childrenDefault` (`default.tsx`) requirement, but only for layout-only parent routes.** When a parent HAS a children page, `default.tsx` is still required as a fallback for the synthetic sub-route. Layout-only parents do not need this fallback because the children slot was never occupied at the parent level. 4. **Add structural-conflict detection before creating synthetic routes.** This prevents layout-only root routes from generating synthetic routes that collide with existing page routes differing only by param name (e.g. `/shop/:id` vs `/shop/:name`), which `validateRoutePatterns` rejects. The slot content for the existing route is handled by `findMirroredSlotPage` instead. ## Validation - Added unit test in `tests/routing.test.ts`: "discovers nested parallel slot sub-routes from layout-only parent" (ported from Next.js test suite) - Added integration test in `tests/app-router.test.ts`: "renders nested parallel route from layout-only parent" - Added fixture files under `tests/fixtures/app-basic/app/parallel-nested/` matching the Next.js reference fixture - All 419 routing-related tests pass: - `tests/routing.test.ts` (106 tests) - `tests/app-route-graph.test.ts` (11 tests) - `tests/app-router.test.ts` (301 tests) - `tests/intercepting-routes-build.test.ts` (1 test) - Lint passes with 0 warnings/errors ## Risks / follow-ups - The structural-conflict check uses pattern-part comparison. In theory, two routes could have the same shape but different param names and both be legitimate (e.g. one from a slot, one from a page). Next.js rejects this at build time, so skipping the synthetic route is the safe choice. - This fix only affects route discovery. Render-time slot resolution via `findMirroredSlotPage` is unchanged. ## References - Next.js test: [parallel-routes-and-interception.test.ts#L510](https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts) - Next.js fixture: [app/parallel-nested](https://github.com/vercel/next.js/tree/canary/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:45 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#1053
No description provided.