fix(qwik-router): backtrack from static prefixes and rank matches by specificity - #8899
Open
blakeley wants to merge 1 commit into
Open
fix(qwik-router): backtrack from static prefixes and rank matches by specificity#8899blakeley wants to merge 1 commit into
blakeley wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: 96c8091 The changes in this PR will be included in the next version bump. This PR includes changesets to release 5 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@qwik.dev/core
@qwik.dev/router
eslint-plugin-qwik
create-qwik
@qwik.dev/optimizer
@qwik.dev/devtools
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #8893
Two symptoms
A static prefix dead-ends the match. The matcher walks the route trie greedily: at each segment it takes the first child that matches (exact →
_Mgroups →_W→_A) and never reconsiders. So withroutes/(marketing)/pricing/index.tsxandroutes/(app)/[a]/[b]/[c]/index.tsx, the URL/pricing/x/ymatchespricingon the first segment, dead-ends under it, and 404s — even though[a]/[b]/[c]matches the whole URL.No specificity ranking across groups. When two branches both match a URL, the winner is whichever the trie happens to be walked into first, so it depends on group ordering.
[x]/static.xmlin one group and[a]/[b]in another both match/foo/static.xml, and which one wins is an accident of_Morder rather than the more specific route.Approach
matchRouteTreenow resolves the best complete match up front, then replays it:findChildAllreturns every candidate child for a segment (in the same priority orderfindChildalready walks) rather than just the first.findMatchChaindoes a DFS over those candidates, only accepting a chain that consumes the whole pathname and lands on a node that actually has a route — so a static prefix that dead-ends no longer hides a dynamic sibling.compareChainsranks complete chains lexicographically by per-segment kind (static < dynamic < catch-all), so the most specific match wins regardless of group order.When no chain matches,
findMatchChainreturns undefined and the segment loop runs exactly as before — the greedy walk, the rest-wildcard fallback, and the 404/boundary recovery are untouched, so unmatched URLs keep producing the same routeParts, params, and error boundaries they do today.One existing rule is preserved deliberately: an exact route subtree still owns its unmatched descendants, so a dead end under a static route 404s rather than being swallowed by a sibling
[...rest](covered by the existingexact child dead end does not fall back to sibling _M catchalltest).Repro
Minimal reproduction: https://github.com/blakeley/router-matcher-no-backtracking
Tests
Two cases added to
packages/qwik-router/src/runtime/src/routing.unit.ts, both of which fail onmainand pass with this change:[a]/[b]/[c]in another —/pricing/x/yreaches the dynamic route instead of 404ing[x]/static.xmlbeats[a]/[b]for/foo/static.xmlin both group ordersThe rest of the existing matcher suite (the static route itself still winning, plain dynamic matches, catch-all fallback, and an exact dead end not falling through to a sibling catch-all) is unchanged and still green, which is what pins the behaviour this change must not alter.
This restores the documented Qwik 1 routing semantics, where static pages and dynamic route families can share a URL prefix and the most specific route wins.