Files
James Anderson 9194cb55c6 fix(pages-router): hard-navigate to App Router destinations from Pages Router links (#1879)
* fix(pages-router): hard-navigate to App Router destinations from Pages Router links

When a Pages Router <Link> targets an App Router route (detected via the
prefetch manifest and recorded as `components[path] = { __appRouter: true }`),
`performNavigation` now checks for that marker and immediately calls
`window.location.assign/replace` instead of attempting a Pages Router SPA
fetch cycle that cannot succeed.

Mirrors Next.js router.ts:1448-1453. Fixes the deploy-suite test
`pages-to-app-routing > should work using browser`.

- packages/vinext/src/shims/router.ts: add __appRouter hard-nav guard
- tests/fixtures/app-basic/app/about/page.tsx: add id="app-page" to <h1>
- tests/fixtures/app-basic/pages/pages-to-app/[slug].tsx: new GSSP fixture page
- tests/e2e/app-router/pages-to-app-routing.spec.ts: new Playwright regression test

* fix(pages-router): replace no-explicit-any cast with in-operator type narrowing

`(getPagesRouterComponentsMap()[_appPath] as any)?.__appRouter` triggered
the `no-explicit-any` lint rule. Replace with a two-step lookup that uses
the `in` operator so TypeScript narrows the union type
`{ __appRouter: true } | Record<string, unknown>` without an `any` cast.

* fix(pages-router): normalize trailing slash in __appRouter marker key, rename locals, pin fast-path test

- Strip trailing slash on both the write side (markAppRouteDetectedOnPrefetch)
  and the read side (performNavigation) so the marker key agrees when
  trailingSlash:true adds a slash to the prefetch URL but not to the nav URL
  (or vice-versa via router.prefetch with a raw un-normalised path).
  Mirrors Next.js: removeTrailingSlash(removeBasePath(pathname)) at router.ts:1442.
- Rename _appPath/_appPathEntry to appPath/appPathNorm/appPathEntry (they are
  used, not ignored; the underscore prefix was misleading).
- Add two new unit assertions in pages-router-app-prefetch-detection.test.ts:
  one pins that the marker key is always stored without trailing slash, and one
  pins that performNavigation calls window.location.assign (the fast-path) when
  the __appRouter marker is present, so a regression in the new code path cannot
  be silently masked by the pre-existing fallback.

* test(pages-router): address review nits in pages-to-app navigation tests

- e2e: wait for App Router hydration after the hard navigation so the
  test asserts the App Router landing explicitly instead of only
  matching the new DOM
- unit: drop the unnecessary microtask tick before asserting
  window.location.assign — the fast path has no await before it, so
  asserting synchronously also pins that the hard navigation cannot
  lose a race against the SPA path
2026-06-11 01:01:54 +01:00
..