test(app-router): cover cross-path hash push URL replacement - #2926
test(app-router): cover cross-path hash push URL replacement#2926NathanDrake2406 wants to merge 1 commit into
Conversation
Port the Next.js `hash-cross-path-push` navigation regression: a cross-path client push carrying a hash, followed by a same-pathname hash change, must replace the hash rather than concatenate it (`/destination#foo#baz`). vinext already behaves correctly because it resolves every navigation target against the live location and writes that resolved href to history, instead of keeping a per-route canonical URL shared across hash targets. Cover both navigation entry points (`<Link>` and `router.push`) so the property stays enforced.
|
@codex review |
commit: |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Closing: this turned out to be parity coverage for a defect vinext does not have, and it is not worth a PR on its own. The triage summary is on #2809. In short, the Next.js bug is specific to its segment cache storing one canonical URL per route entry shared across every hash target; vinext resolves each navigation target against the live location and writes that resolved href to history, so the hash is replaced rather than concatenated. The upstream scenario was reproduced against No production changes were proposed here. |
Summary
hash-cross-path-pushnavigation regression as an App Router browser testnextjs-compatfixture routes (start page + destination page)<Link>androuter.pushCloses #2809.
Context
vercel/next.js#93132 fixes a client-navigation bug where the URL hash is
appended twice (
/abc#foo#bar). Its cause is specific to the Next.js segmentcache:
createRouteCacheEntrystored one canonical URL per route entry, sharedacross every hash target of that route, and a later same-route hash navigation
appended
url.hashto it. The upstream fix stores a hashless canonical href.Triage
The defect does not reproduce in vinext, and the reason is structural rather
than incidental.
rg 'canonicalUrl' packages/vinext/srcmatches only the Sass plugin.
(
toBrowserNavigationHref->resolveRelativeHref,shims/url-utils.ts), andthe committed history write uses that resolved target
(
AppBrowserHistoryController.commitNavigationHistory,createCanonicalBrowserHistoryHref). Nothing concatenates a new hash onto astored href from an earlier navigation.
main(<Link>clicks,router.push, and a search+hash variant): all landed on the correct singlehash.
Existing coverage did not reach this case.
hash-popstate-scroll.spec.tsandhash-rsc-requests.browser.spec.tsonly change hashes after a direct documentload of the route; the upstream regression requires the route entry to be
created by a cross-path client push that already carries a hash.
Since the behavior is correct, this follows the precedent of #2758: a
tracking issue resolved with focused regression coverage rather than a
production change.
Verification
vp checkon the five new files: pass (format, lint, types)PLAYWRIGHT_PROJECT=app-router npx playwright test hash-cross-path-push.spec.ts: 2/2 passed--repeat-each=5 --retries=0): 10/10 passedhash-popstate-scroll.spec.ts+router-autoscroll.spec.ts: 38/38 passedMutation check (the tests have real signal). Temporarily injected the
upstream failure mechanism into
navigateClientSide'ssameDocumentScrollbranch — committing
location.pathname + location.search + location.hash + intent.hashinstead of the resolved target — then rebuiltvinext. Both newtests failed with exactly the reported malformed URL:
The mutation was reverted and
vinextrebuilt before committing; the workingtree contains no production changes (
git diffagainstpackages/is empty).Scope
Test and fixture only — no runtime behavior changes, so no changeset. The
Pages Router hash lifecycle (
shims/router.ts,hashChangeStart) is a separatenavigation path and is out of scope for this upstream port.