fix(router): derive shallow dynamic params from the URL#1272
Conversation
Pages Router shallow navigation across one dynamic route template updated the browser URL but kept router.query params from the original __NEXT_DATA__ payload. That left client state stale after transitions such as /posts/42 to /posts/43. The router now matches the current pathname against the hydrated route pattern before falling back to __NEXT_DATA__.query. This preserves shallow data-fetch skipping while keeping dynamic params aligned with the visible URL. Tests cover the router shim boundary and the browser-visible Pages Router shallow transition.
commit: |
The shallow dynamic params fix originally duplicated route-pattern parsing inside the router shim. Reuse the existing routePatternParts and matchRoutePattern helpers instead, keeping the __NEXT_DATA__ fallback behavior unchanged.
|
/bigbonk review |
There was a problem hiding this comment.
Clean, well-scoped fix. The approach of deriving dynamic params from the current URL via matchRoutePattern (with a fallback to hydrated __NEXT_DATA__.query) is correct and matches how Next.js handles this in router.ts lines 1537-1590.
The layering is good:
- Try to match
window.location.pathnameagainst__NEXT_DATA__.pagepattern — this handles the shallow navigation case where the URL changed but__NEXT_DATA__.queryis stale. - Fall back to reading param names from
__NEXT_DATA__.query— this covers edge cases where the URL doesn't match the pattern (e.g., middleware rewrites that change the visible URL shape).
Reusing matchRoutePattern + routePatternParts from routing/route-pattern.ts avoids duplicating pattern-matching logic. The decodeMatchedParams call inside matchRoutePattern correctly handles percent-encoded URL segments.
Tests are thorough: the unit test verifies that __NEXT_DATA__.query stays stale (confirming no mutation) while router.query reflects the new URL, and the E2E test confirms the full browser scenario including that props stay frozen (shallow) while router state updates.
Two minor observations below, neither blocking.
| @@ -300,6 +301,46 @@ function extractRouteParamNames(pattern: string): string[] { | |||
| return names; | |||
| } | |||
There was a problem hiding this comment.
Nit: extractRouteParamNames is now only called inside getRouteQueryFromNextData (the fallback path). If you wanted to tighten things up in a follow-up, you could consider inlining it there or just noting that it's exclusively used for the fallback. Not blocking — the current structure is clear enough.
| __NEXT_DATA__: { | ||
| page: "/posts/[id]", | ||
| query: { id: "42" }, | ||
| isFallback: false, | ||
| }, |
There was a problem hiding this comment.
Nice detail: the test intentionally keeps __NEXT_DATA__.query stale at { id: "42" } and asserts it stays unchanged (line 10397), which proves the fix derives params from the URL rather than mutating the hydration payload. This is exactly the right thing to verify.
|
Review submitted and approved. Here's a summary of my findings: Verdict: Approve The fix correctly addresses stale
This matches how Next.js handles it in their Tests are well-structured — the unit test proves |
…namic-query # Conflicts: # packages/vinext/src/shims/router.ts
Overview
__NEXT_DATA__.pagebefore falling back to hydrated__NEXT_DATA__.query.router.queryfor dynamic templates.packages/vinext/src/shims/router.ts,tests/shims.test.ts,tests/e2e/pages-router/shallow-routing.spec.ts/posts/42to/posts/43with{ shallow: true }now reportsrouter.query.id === "43"without refetching page props.Why
For Pages Router shallow navigation to be correct, the router state must reflect the visible URL even when data fetching is intentionally skipped. The previous implementation reused route params from the initial hydration payload, so same-template dynamic shallow transitions changed
asPathbut leftquerystale.window.location.pathnamewhen it matches the hydrated route pattern.router.query.What changed
/posts/42shallow-pushes to/posts/43router.querystayed{ id: "42" }router.querybecomes{ id: "43" }Maintainer review path
packages/vinext/src/shims/router.ts: review the route-pattern parsing and fallback path ingetRouteQueryFromNextData.tests/shims.test.ts: review the focused red/green router-state regression that leaves__NEXT_DATA__.queryintentionally stale.tests/e2e/pages-router/shallow-routing.spec.ts: review the browser-visible regression proving props stay shallow-frozen whilerouter.queryupdates.Validation
vp test run tests/shims.test.ts -t "updates dynamic route params from the URL after shallow navigation"vp test run tests/shims.test.ts -t "Pages Router router"vp test run tests/shims.test.tsPLAYWRIGHT_PROJECT=pages-router pnpm exec playwright test tests/e2e/pages-router/shallow-routing.spec.ts -g "dynamic route params update"vp checkRisk / compatibility
Non-goals
{ shallow: true }to skip client fetching.References
next.router.query.slugfromfirsttosecond.pathnameandquerywithout rerunning data fetching methods.