mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #420] [MERGED] fix: reject conflicting dynamic route siblings during route discovery #556
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#556
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/cloudflare/vinext/pull/420
Author: @JaredStowell
Created: 3/10/2026
Status: ✅ Merged
Merged: 3/10/2026
Merged by: @james-elicx
Base:
main← Head:jstowell/fix-pages-router-dynamic-conflicts📝 Commits (2)
c61f8d5Validate route patterns in routers6f9f253Validate intercepting routes📊 Changes
5 files changed (+420 additions, -6 deletions)
View changed files
📝
packages/vinext/src/routing/app-router.ts(+10 -0)📝
packages/vinext/src/routing/pages-router.ts(+6 -6)➕
packages/vinext/src/routing/route-validation.ts(+163 -0)📝
tests/route-sorting.test.ts(+217 -0)📝
tests/routing.test.ts(+24 -0)📄 Description
Reject ambiguous dynamic route siblings during route discovery instead of silently accepting them and resolving by first-match wins.
This fixes the case where routes like
pages/posts/[id].tsxandpages/posts/[slug].tsxwere both registered even though they represent the same URL shape. A request like/posts/42would match whichever route sorted first, which diverges from Next.js behavior.Bug
Before this change, vinext treated these as distinct internal patterns:
pages/posts/[id].tsx->/posts/:idpages/posts/[slug].tsx->/posts/:slugBecause the param name was preserved in the internal pattern, both routes were accepted and later matched independently. At request time,
/posts/42matched one of them by scan/sort order.That is ambiguous. The param name differs, but the route shape does not.
Next.js rejects this configuration during route validation instead of allowing runtime first-match behavior.
What changed
sorted-routespatternToNextFormat()into the shared validation module and re-exported it frompages-router.tsExample
Given:
pages/posts/[id].tsxpages/posts/[slug].tsxBefore:
/posts/42resolved to one file by first-match winsAfter:
Next.js reference
Adapted from:
packages/next/src/shared/lib/router/utils/sorted-routes.tstest/unit/page-route-sorter.test.tsVerification
Reproduced the bug on current
upstream/mainin a clean worktree:/posts/:idand/posts/:slugwere registered/posts/42matched one file instead of rejecting the conflictValidated the fix on top of current
upstream/mainwith:pnpm test tests/route-sorting.test.ts tests/routing.test.ts tests/page-extensions-routing.test.tspnpm run typecheckpnpm run fmt:check tests/route-sorting.test.ts packages/vinext/src/routing/pages-router.ts packages/vinext/src/routing/app-router.ts packages/vinext/src/routing/route-validation.tsNotes
This is intentionally scan-time validation, not match-time handling. The goal is to reject ambiguous route definitions before route precedence can hide the conflict.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.