fix(router): dedupe gssp data navigations#2102
Open
NathanDrake2406 wants to merge 8 commits into
Open
Conversation
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.
commit: |
- 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
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
_next/dataresponses across concurrent navigations, keep cancelled requests joinable, and gate data prefetch to SSG routes.pages-data-fetch-dedup.ts->pages-data-target.ts->router.ts-> tests.Why
Pages Router data navigation has two distinct contracts in Next.js:
fetchNextData()dedupes by data URL, andRouter.prefetch()fetches_next/dataonly 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
_next/datafetches could call gSSP repeatedly.getStaticPropsprefetch data; all Pages routes still warm the chunk.__N_SSPclearing point.__N_SSPentries 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.tsCI=1 PLAYWRIGHT_PROJECT=pages-router-prod vp env exec --node 24 pnpm run test:e2e tests/e2e/pages-router-prod/gssp-data-dedup.spec.tsvp checkvp 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.tsshould not trigger an error when a data request is cancelled due to another navigation,should dedupe server data requests.__NEXT_DATA__.gssp, invalid JSON GSSP handling.tests/entry-templates.test.ts, staged checks, full check, staged tests, knip.References
#slowclicks and expectedhit: 1thenhit: 2.fetchNextDataRouter.prefetchpageLoader._isSsg(route).