Skip to content

Return leaf selected layout segment for named parallel routes#909

Merged
james-elicx merged 1 commit intocloudflare:mainfrom
NathanDrake2406:nathan/fix-selected-layout-segment-parallel-routes
Apr 26, 2026
Merged

Return leaf selected layout segment for named parallel routes#909
james-elicx merged 1 commit intocloudflare:mainfrom
NathanDrake2406:nathan/fix-selected-layout-segment-parallel-routes

Conversation

@NathanDrake2406
Copy link
Copy Markdown
Contributor

What this changes

useSelectedLayoutSegment(parallelRoutesKey) now matches Next.js for named parallel routes: the default children route still returns the first child segment, while named slots return the active leaf segment from that slot path.

This fixes nested parallel slot routes such as @team/members/profile, where useSelectedLayoutSegments("team") returns ["members", "profile"] and useSelectedLayoutSegment("team") should return "profile".

Why

Vinext previously reduced every segment array with segments[0]. That is only correct for the default children route. Next.js intentionally treats named parallel route keys differently in computeSelectedLayoutSegment():

  • children: first segment
  • non-children parallel route keys: last segment

Source references:

Approach

The shim now returns null for an empty segment list, returns the first segment for undefined or "children", and returns the final segment for named parallel route keys.

The regression test adds a nested @team slot fixture and asserts both behaviours together:

  • useSelectedLayoutSegments("team") exposes the full slot path ["members", "profile"]
  • useSelectedLayoutSegment("team") returns the leaf segment "profile"

Validation

  • vp test run tests/app-router.test.ts -t "useSelectedLayoutSegment"
  • vp check tests/app-router.test.ts packages/vinext/src/shims/navigation.ts tests/fixtures/app-basic/app/dashboard/@team/members/profile/page.tsx

Risks / follow-ups

This is scoped to the client navigation shim reduction rule. It does not change route discovery or segment-map construction, which already preserves the full slot segment path needed for this behaviour.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 26, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@909

commit: dd6f56f

@NathanDrake2406 NathanDrake2406 marked this pull request as ready for review April 26, 2026 07:47
Copilot AI review requested due to automatic review settings April 26, 2026 07:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Aligns useSelectedLayoutSegment(parallelRoutesKey) behavior with Next.js for named parallel routes by returning the leaf segment for named slots while keeping first child behavior for the default children route.

Changes:

  • Update useSelectedLayoutSegment() reduction logic to return the last segment for non-children parallel route keys.
  • Add a nested @team/members/profile fixture to exercise nested named parallel slot routing.
  • Add an integration test asserting both useSelectedLayoutSegments("team") and useSelectedLayoutSegment("team") behaviors.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
tests/fixtures/app-basic/app/dashboard/@team/members/profile/page.tsx Adds a nested named slot leaf page fixture (profile).
tests/app-router.test.ts Adds regression coverage for leaf segment selection in nested named slots.
packages/vinext/src/shims/navigation.ts Implements Next.js-aligned segment selection for named parallel routes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/app-router.test.ts Outdated
Comment on lines +443 to +445
expect(html).toMatch(
/data-testid="team-segments"[^>]*>\["members","profile"\]/,
);
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

This assertion is tightly coupled to the exact HTML entity encoding and formatting of the rendered array (e.g., ", spacing, potential newlines). That makes the test brittle to unrelated rendering/serialization changes. Prefer extracting the data-testid=\"team-segments\" element’s text content and asserting on a normalized value (e.g., parse JSON / compare arrays), or relax the regex to tolerate whitespace and different escaping.

Copilot uses AI. Check for mistakes.
useSelectedLayoutSegment(parallelRoutesKey) returned the first segment for every route key. That matches children routes, but it misreports nested named parallel routes such as a slot path with members/profile by returning members instead of the active leaf profile.

The root cause was treating the array returned by useSelectedLayoutSegments() as having the same reduction rule for children and named slots. Match Next.js by keeping first-segment behaviour for children and using the final segment for non-children parallel route keys.

Adds a nested @team fixture route that mirrors Next.js parallel-routes-use-selected-layout-segment coverage.
@NathanDrake2406 NathanDrake2406 force-pushed the nathan/fix-selected-layout-segment-parallel-routes branch from e6d3ea4 to dd6f56f Compare April 26, 2026 08:20
@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Nice fix — the implementation matches Next.js's computeSelectedLayoutSegment() exactly (source) for the cases vinext exercises.

Notes:

  • The early return null for empty segments correctly subsumes Next.js's DEFAULT_SEGMENT_KEY check, because vinext omits default-slot segments entirely rather than using a __DEFAULT__ sentinel (verified via grep — no __DEFAULT__ references in the codebase). If vinext ever introduces a sentinel for default slots, this hook will need to be revisited.
  • The new textContentByTestId + JSON.parse helper neatly addresses the brittleness Copilot flagged in earlier "-based regex assertions, and tightens the useSelectedLayoutSegments checks across the file.
  • Test coverage hits the exact case from the linked Next.js E2E (useSelectedLayoutSegments("team")["members", "profile"], useSelectedLayoutSegment("team")"profile").
  • Fixture layout is sensible: @team/members/profile/page.tsx reuses the existing @team slot and the children slot falls back to default.tsx on /dashboard/members/profile (no app/dashboard/members/... children page exists). That matches what the existing /dashboard/members tests already rely on.

Only nit below — non-blocking.

Comment thread tests/app-router.test.ts
expect(res.status).toBe(200);
const html = await res.text();

expect(html).toContain('data-testid="team-member-profile-page"');
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.

Minor: this toContain is redundant with the segment assertions on the next two lines — if team-segments resolves to ["members", "profile"], the leaf page must have rendered. Not worth blocking on; feel free to keep it as a readability sanity check or drop it.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Apr 26, 2026

Review posted: #909 (review)

Summary

The PR is a clean, well-targeted fix that aligns useSelectedLayoutSegment(parallelRoutesKey) with Next.js's computeSelectedLayoutSegment():

  • Implementation correctness: The 3-line change in navigation.ts matches Next.js's source exactly — empty → null, children/undefined → first segment, named slots → last segment.
  • __DEFAULT__ sentinel: Next.js also returns null when the segment equals DEFAULT_SEGMENT_KEY. Vinext doesn't use this sentinel (verified via grep) — defaults are omitted entirely, so the empty-array early return covers it. Flagged as a forward-looking note.
  • Test quality: The new textContentByTestId helper + JSON.parse assertions also address the brittleness Copilot called out, by replacing all the HTML-entity-coupled regexes in this test block.
  • Fixture: Sensibly reuses the existing @team slot infrastructure; the new /dashboard/members/profile route relies on the children slot falling back to default.tsx (consistent with how the existing /dashboard/members tests already work).

Posted one non-blocking nit about a redundant toContain check that duplicates the segment assertions.

github run

@james-elicx james-elicx merged commit 2f7e5cf into cloudflare:main Apr 26, 2026
24 checks passed
@NathanDrake2406 NathanDrake2406 deleted the nathan/fix-selected-layout-segment-parallel-routes branch April 26, 2026 15:07
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.

3 participants