Commit Graph

2 Commits

Author SHA1 Message Date
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
Divanshu Chauhan (divkix) d9a5e63c32 refactor(pages): extract render orchestrator from codegen string (#1784) (#1805)
* refactor(pages): extract render orchestrator from codegen string (#1784)

Move `_renderPage` (~260 lines) and `collectAssetTags` (~85 lines) out
of the generated template string in `entries/pages-server-entry.ts` into
typed, unit-testable server modules:

- `server/pages-page-handler.ts` — `createPagesPageHandler` factory that
  owns the full Pages Router render lifecycle (i18n, route matching,
  404/500 fallback recursion, _next/data envelope, 405 method check,
  ISR cache-key derivation, SSR-context setup). Mirrors how
  `server/app-rsc-handler.ts` is structured relative to
  `entries/app-rsc-entry.ts`.

- `server/pages-asset-tags.ts` — `collectAssetTags` + manifest helpers,
  extracted from the template so they can be unit-tested directly.

- `server/pages-data-route.ts` — adds `normalizePagesDataRequest` so both
  `renderPage` and `runMiddleware` share one implementation instead of
  duplicating the buildId-check logic in the template string.

`entries/pages-server-entry.ts` is now thin wiring: route-table codegen,
embedded config/i18n/buildId, and closures that delegate to the handler.
`renderPage` keeps its exact exported signature; all callers are unaffected.

No behavior change — pure code motion. Fixed a latent bug where the
`isrCacheKey` wrapper in the handler was missing the `buildId` argument on
non-i18n paths (causing a cache-key mismatch); caught by the existing
ISR CSP-nonce test.

* refactor(pages): tighten handler types and improve tests

- Use ComponentType from react for AppComponent, DocumentComponent, and
  PageComponent closures instead of unknown — matches pages-page-response.ts
  which already uses ComponentType. Eliminates the DocumentComponent cast.

- Widen isrCacheKey router param from literal union to string — the literal
  union forced as-casts everywhere it was used indirectly. All call sites
  pass 'pages' or 'app' string literals so the widening is safe.

- Replace the shallow i18n redirect test (which just checked a route miss
  gave 404) with a test that actually exercises the 307 short-circuit:
  visiting / with Accept-Language: fr triggers resolvePagesI18nRequest to
  return redirectUrl and the handler returns 307 to /fr.

- Add second i18n test: locale prefix already present → no redirect.

- Tighten 'allows POST with gSSP' test: assert no Allow header (confirms
  the 405 guard was bypassed, not just that some non-405 came back).
2026-06-07 22:18:12 +01:00