Commit Graph

3 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
Nathan Nguyen 340da2dd19 refactor(server): extract pages fallback bridge into typed helper (#1808)
* refactor(server): extract pages fallback bridge into typed helper

* test(app-router): update pages fallback codegen assertion

* refactor(server): tighten buildRequestHeaders type to Headers | null

The injected buildRequestHeaders dependency wires in
buildRequestHeadersFromMiddlewareResponse, which returns Headers | null,
but the dependency type declared a non-nullable Headers return. Tighten
the type to match reality and add a test exercising the null path where
the original request is forwarded unchanged.

---------

Co-authored-by: James <james@eli.cx>
2026-06-07 22:04:54 +01:00
Nathan Nguyen e372ff336a test: rebalance app router integration shards (#1765)
Vitest integration CI shards by test file, so the 340-test App Router integration file becomes one oversized scheduling unit. That makes the slowest shard depend on one monolithic file even after adding native shards.

Split the App Router integration coverage into focused files and increase the integration matrix to four native Vitest shards. This keeps fileParallelism disabled for shared-fixture safety while giving CI smaller scheduling units to distribute.

Validation: vp check tests/app-router-*.test.ts vite.config.ts .github/workflows/ci.yml; vp test run --project integration tests/app-router-client-preloading.test.ts tests/app-router-dev-server.test.ts tests/app-router-external-rewrite.test.ts tests/app-router-font-google-prod.test.ts tests/app-router-isr-codegen.test.ts tests/app-router-malformed-url.test.ts tests/app-router-metadata-routes.test.ts tests/app-router-middleware-next-request.test.ts tests/app-router-next-config-codegen.test.ts tests/app-router-next-config-dev.test.ts tests/app-router-origin-check.test.ts tests/app-router-production-build.test.ts tests/app-router-production-server.test.ts tests/app-router-rsc-flight-hint.test.ts tests/app-router-rsc-plugin.test.ts tests/app-router-static-export.test.ts tests/app-router-worker-entry.test.ts
2026-06-05 15:48:54 +01:00