Files
Nathan Nguyen 3a1e56748f fix(app-router): match Pages navigation params in hybrid builds (#1741)
* fix(app-router): emit pages client entry for hybrid builds

* fix pages router navigation params readiness

* fix(pages-router): track hashed fallback client entry

* fix(pages-router): honor navigation readiness in hooks

* fix(pages-router): derive server router.isReady from SSR readiness

Address review feedback on #1741:

- getRouterSnapshot() now reads navigationIsReady from the SSR context on
  the server instead of unconditionally returning true. For pre-ready Pages
  routes (auto-export dynamic / query-string / rewrite-capable builds) this
  keeps useRouter().isReady consistent between SSR and client hydration,
  avoiding a hydration mismatch for components that read it in JSX. Mirrors
  Next.js render.tsx's server readiness rule.
- Drop the redundant guard branch in getClientParamsSnapshot.
- Add a regression test asserting server useRouter().isReady reflects the
  SSR navigationIsReady context.

* fix(pages-router): align param resolution and dev/prod readiness parity

Address review feedback (ask-bonk + self-review) on #1741:

- Align client Pages navigation params to query-first, matching the server
  snapshot and Next.js's adaptForPathParams (derives path params from
  router.query). __NEXT_DATA__.query is kept current on client navigation, so
  the two snapshots are now provably identical and avoid a latent post-
  hydration mismatch.
- Populate window.__VINEXT_PAGE_PATTERNS__ in the dev server (own script tag)
  so the next/navigation compat hooks can resolve a dynamic pattern from a
  resolved path in dev exactly as in production. Closes a dev/prod parity gap
  (prod set this via the client entry; dev never did).
- Drop the redundant pre-readiness setSSRContext in dev; the single post-
  module-load call now publishes navigationIsReady before rendering, matching
  the prod handler.
- Document that installPagesClientAssetGlobals is intentionally inert for
  App-only builds, and that getReadonlyPagesSearchParams' string-keyed cache
  is safe to share across concurrent SSR requests (immutable wrappers).
- Add a dev regression test asserting __VINEXT_PAGE_PATTERNS__ exposure.

* fix(pages-router): prefer URL params for client snapshots

* fix(app-router): gate pages asset globals

* test(app-router): add production test for hybrid basePath + assetPrefix

* fix(navigation): widen usePathname return type to string | null

* refactor(pages-router): dedupe navigation readiness modeling and cache pattern scan

Addresses code-review cleanup findings on the Pages Router navigation
readiness work:

- Extract buildPagesReadinessNextData() so the dev SSR handler and the
  production Pages page handler derive the gssp/gsp/gip/appGip/autoExport
  __NEXT_DATA__ readiness flags from one place, instead of two hand-kept
  copies that could drift and produce a dev/prod hydration mismatch.
- Extract resolvePagesNavigationPathname() shared by the client and SSR
  branches of getPagesNavigationContext, so the pathname null-ness rule
  (fallback / pre-ready auto-export dynamic) cannot diverge between them.
- Memoize resolvePagesRoutePatternForPath()'s pattern scan, which ran an
  uncached O(routes) loop on every useSyncExternalStore snapshot read.
- In getServerSearchParamsSnapshot, serialize the source only when the
  object-identity fast path misses, avoiding a toString() on every read.

* refactor(pages-router): consistent ISR readiness data + robustness cleanups

Addresses further code-review findings:

- Thread the readiness flags (gssp/gsp/gip/appGip/autoExport) through the ISR
  background-regeneration path: renderPagesIsrHtml now serializes the same
  __NEXT_DATA__ readiness state the initial render emits, so a regenerated,
  re-cached page hydrates with the identical initial router.isReady the server
  computed. Previously these flags were dropped on that path — benign today
  (ISR-eligible getStaticProps routes never trigger the flag-gated early-ready
  branches) but a latent dev/prod hydration-readiness drift. Adds a shared
  PagesNextDataExtras type so every render path stays in lockstep.
- readSsrManifest: degrade a malformed/unparseable SSR manifest to "no SSR
  manifest" with a warning instead of throwing and aborting server startup,
  restoring the previous tolerant behavior.
- pages-readiness: project PagesReadinessNextData from the canonical
  VinextNextData and accept PagesPageModule, instead of bespoke types that
  could drift from the __NEXT_DATA__ shape.
- getReadonlyPagesSearchParams: drop the redundant string-keyed cache slot; the
  per-object WeakMap is authoritative and concurrency-safe on its own.
- isPagesRouterReady: drop the redundant server-side window check.

* fix(pages-router): restore string-keyed readonly search-params cache

Reverts the cache simplification from the previous commit. The string-keyed
slot is load-bearing, not redundant: across the Pages Router pre-ready → ready
transition the navigation context swaps in a new URLSearchParams instance even
when the query string is unchanged, so the per-object WeakMap alone returns a
fresh ReadonlyURLSearchParams wrapper and useSearchParams() loses Object.is
stability — re-firing a [searchParams] effect. Caught by the rewrite-driven
pre-ready static page e2e (search-params-snapshots saw ['',''] not ['']).
Keeps the improved comment explaining why both cache levels are needed.

---------

Co-authored-by: James <james@eli.cx>
2026-06-09 11:30:38 +01:00
..