Skip to content

fix(router): dedupe gssp data navigations#2102

Open
NathanDrake2406 wants to merge 8 commits into
cloudflare:mainfrom
NathanDrake2406:nathan/gssp-e2e-parity
Open

fix(router): dedupe gssp data navigations#2102
NathanDrake2406 wants to merge 8 commits into
cloudflare:mainfrom
NathanDrake2406:nathan/gssp-e2e-parity

Conversation

@NathanDrake2406

Copy link
Copy Markdown
Contributor

Overview

Area Summary
Goal Match Next.js Pages Router behavior for rapid getServerSideProps client navigations.
Core change Share buffered _next/data responses across concurrent navigations, keep cancelled requests joinable, and gate data prefetch to SSG routes.
Review path pages-data-fetch-dedup.ts -> pages-data-target.ts -> router.ts -> tests.
Impact Repeated clicks to the same gSSP Link run one server data request; later navigation fetches fresh props.

Why

Pages Router data navigation has two distinct contracts in Next.js: fetchNextData() dedupes by data URL, and Router.prefetch() fetches _next/data only for SSG pages. gSSP pages should not eagerly consume server data during prefetch, but concurrent navigations to the same gSSP URL should still share the active request.

What changed

Scenario Before After
Rapid repeated gSSP Link clicks Multiple _next/data fetches could call gSSP repeatedly. Concurrent callers share one buffered data request.
Cancelled data navigation Cancelling waiters could abandon the shared request shape. Cancellation rejects that caller while the shared request remains joinable.
Pages Link prefetch gSSP routes could prefetch data too early. Only routes exporting getStaticProps prefetch data; all Pages routes still warm the chunk.
Completed gSSP navigation Cache lifetime did not match Next's __N_SSP clearing point. __N_SSP entries clear after successful consumption so the next navigation fetches fresh props.
Validation
  • vp test run tests/pages-data-fetch-dedup.test.ts tests/pages-data-prefetch.test.ts
  • CI=1 PLAYWRIGHT_PROJECT=pages-router-prod vp env exec --node 24 pnpm run test:e2e tests/e2e/pages-router-prod/gssp-data-dedup.spec.ts
  • vp check
  • vp env exec --node 24 ./scripts/run-nextjs-deploy-suite.sh /Users/nathan/Projects/vinext/.refs/nextjs-v16.2.6 --retries 0 -c 1 --debug test/e2e/getserversideprops/test/index.test.ts
    • Selected block now passes: should not trigger an error when a data request is cancelled due to another navigation, should dedupe server data requests.
    • Remaining failures are out of scope: 404 client-transition visibility, non-GSSP __NEXT_DATA__.gssp, invalid JSON GSSP handling.
  • Pre-commit: tests/entry-templates.test.ts, staged checks, full check, staged tests, knip.

References

Reference Why it matters
Next.js gSSP e2e test Source behavior for repeated #slow clicks and expected hit: 1 then hit: 2.
Next.js fetchNextData Defines the in-flight data cache and cancellation-independent fetch promise.
Next.js Router.prefetch Shows data prefetch is gated on SSG via pageLoader._isSsg(route).

Rapid repeated Pages Router navigations to the same getServerSideProps route could start multiple _next/data requests or reuse an eager prefetch at the wrong time. That diverges from Next.js, where gSSP data is fetched on navigation and concurrent callers share the same fetchNextData promise.

The root cause was treating Pages data prefetch and navigation dedupe as the same cache shape for every route. The fix adds an SSG route manifest for data prefetch gating, buffers shared data responses for independent consumers, keeps cancelled waiter requests joinable, and clears __N_SSP entries after a successful navigation consumes them.

Regression coverage ports the upstream repeated Link-click gSSP behavior and keeps the existing cancellation coverage.
@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2102
npm i https://pkg.pr.new/vinext@2102

commit: 3d716ec

- x-nextjs-redirect responses could be cached indefinitely when persist=true
- delete the cached entry after handling a soft redirect
- update stale cancellation comment in resolveMiddlewareDataEffect
- add regression test for repeated redirect data navigation
- add unit test for deletePagesDataCacheEntry
@NathanDrake2406 NathanDrake2406 marked this pull request as ready for review June 17, 2026 06:14
…ader threading

The Pages Router data-fetch dedup cache derived the deployment-id portion of
its key by re-reading the `x-deployment-id` header off each caller's `init`.
Callers set that header from `getDeploymentId()` in the first place, so the
header round-trip was a self-imposed dependency: the cache-eviction path then
had to thread the original headers back through `navigateClientData` (the
`dataFetchHeaders` variable) just to reconstruct the same key.

Within a realm `getDeploymentId()` is a constant `process.env` read, so the
header value can never diverge from it. Read the source of truth directly in
`getInflightKey`, drop the `init` parameter from `getInflightKey` and
`deletePagesDataCacheEntry`, and remove the `dataFetchHeaders` tracking in
`router.ts`. The prefetched-response eviction path previously passed
`undefined` headers, computing a wrong (empty-deployment) key and failing to
evict; deriving from `getDeploymentId()` fixes that and lets the redirect
guard run unconditionally.

Also fold `hasExportedName`'s AST walk into `hasNamedExportInProgram` via a
`which: "local" | "exported"` parameter, and drop the redundant `.slice(0)` +
zero-byte guard in `responseFromBuffered` (the `Response` constructor already
copies the `ArrayBuffer`).

The dedup test's concurrent two-deployment-id case is removed: it modeled a
scenario that cannot occur in a single realm.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant