Skip to content

fix: support URLPattern modifiers (?, +, *) in non-exact parent routes#222

Merged
uhyo merged 1 commit into
masterfrom
claude/issue-205-qcero2
Jul 11, 2026
Merged

fix: support URLPattern modifiers (?, +, *) in non-exact parent routes#222
uhyo merged 1 commit into
masterfrom
claude/issue-205-qcero2

Conversation

@uhyo

@uhyo uhyo commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Closes #205

Problem

Non-exact (parent) routes computed the consumed pathname prefix by counting pattern segments, assuming one pathname segment per pattern segment. Any URLPattern syntax where the number of matched segments can differ from the number of pattern segments (?, +, * modifiers, regex groups, wildcards) produced a wrong consumed prefix, so children received the wrong remaining pathname and the whole route tree failed to match:

// failed to match /docs/page/5 (with :section absent)
route({ path: "/docs/:section?", component: Layout, children: [
  route({ path: "/page/:id", component: Page }),
]})

// failed to match /files/a/b/meta
route({ path: "/files/:path+", component: Layout, children: [
  route({ path: "/meta", component: Meta }),
]})

Fix

Runtime (matchRoutes.ts): matchPath now returns match candidates instead of a single result.

  • Simple patterns (literal segments and plain :params) keep the original segment-counting fast path, so the common case does no extra work.
  • Patterns with variable-length syntax match the bare pattern (without the {/*}? suffix) against every pathname prefix, longest first. matchRoute backtracks to a shorter consumed prefix when no child matches the remainder, so /docs/:section? matches /docs/page/5 with section absent.
  • Ambiguous matches prefer the longest consumed prefix (greedy), matching URLPattern's own semantics.
  • SSR SKIPPED semantics are preserved: once a candidate's child is skipped, shorter prefixes aren't tried, so the server can't render a different tree than the client would hydrate.

Types (route.ts): ExtractParams now strips modifiers, regex groups, and group delimiters from param names, so path: "/docs/:section?" types params as { section: string } instead of { "section?": string } (also handles :id(\d+) and {/:section}?).

Docs: extended the README Path Patterns table with optional/repeated/regex parameter rows and a note that patterns work as prefixes on parent routes.

Tests

13 new cases: 11 runtime tests pinning ?/+/*/regex-group/brace-group behavior in parent routes (including greedy preference, backtracking, :path+ minimum-segment enforcement, and requireChildren: false fallback) and 2 type-level tests for modifier stripping. All 299 tests pass; typecheck, lint, and format are clean.

Note: pnpm build fails for the docs and example-rsc packages in the dev container with ReferenceError: URLPattern is not defined — this is pre-existing on the base commit (Node 22 has no global URLPattern) and unrelated to this change. The router package builds cleanly.

🤖 Generated with Claude Code

https://claude.ai/code/session_016HWo3h7xx3i3yB7AjoBTpP


Generated by Claude Code

Non-exact (parent) routes computed the consumed pathname prefix by
counting pattern segments, assuming one pathname segment per pattern
segment. URLPattern syntax where the number of matched segments can
differ from the number of pattern segments (optional/repeated params,
regex groups, wildcards) produced a wrong consumed prefix, so child
matching received the wrong remaining pathname and the whole tree
failed to match.

matchPath now returns match candidates. Simple patterns (literal
segments and plain :params) keep the segment-counting fast path. For
patterns with variable-length syntax, the bare pattern (without the
{/*}? suffix) is matched against every pathname prefix, longest first,
and route matching backtracks to a shorter consumed prefix when no
child matches the remainder.

Also fix ExtractParams to strip modifiers, regex groups, and group
delimiters from param names, so path "/docs/:section?" types params as
{ section: string } instead of { "section?": string }.

Closes #205

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HWo3h7xx3i3yB7AjoBTpP
@uhyo
uhyo merged commit 98262fc into master Jul 11, 2026
1 check passed
@uhyo
uhyo deleted the claude/issue-205-qcero2 branch July 11, 2026 14: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.

URLPattern modifiers (?, +, *) in non-exact parent routes break matching entirely

2 participants