From 5901d99cbaba91a8463871ab623373925562a2d0 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 16 May 2025 16:23:08 +0200 Subject: [PATCH 1/2] Use `onPostpone` to determine if segment prefetch is partial When dynamicIO is enabled, missing data is encoded to an infinitely hanging promise, the absence of which we use to determine if a segment is fully static or partially static. However, when dynamicIO is not enabled, this trick doesn't work. Previously, if PPR is enabled, and dynamicIO is not, we were conservative and assumed that all segments are partial. That doesn't need to be the case, though. We can use the `onPostpone` callback of the `prerender` function to determine if a given RSC node is partial. To make sure that this works as expected, we're disabling `dynamicIO` in `test/e2e/app-dir/segment-cache/incremental-opt-in`. In addition, we're also disabling `dynamicIO` in `test/e2e/app-dir/segment-cache/client-only-opt-in` as well as `test/e2e/app-dir/segment-cache/export`, to prepare for an upcoming change where `ppr` will be enabled automatically when `dynamicIO` is enabled. Those three tests are then not compatible with `dynamicIO` because they either rely on the `'incremental'` PPR config, or on `output: 'export'`. --- .../next/src/server/app-render/app-render.tsx | 19 ------------- .../app-render/collect-segment-data.tsx | 27 ++++++------------- .../client-only-opt-in/next.config.js | 1 - .../segment-cache/export/next.config.js | 2 -- .../incremental-opt-in/next.config.js | 1 - test/ppr-tests-manifest.json | 1 + 6 files changed, 9 insertions(+), 42 deletions(-) diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 238f3175b3f8..3f127d1763dd 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -4258,27 +4258,8 @@ async function collectSegmentData( serverModuleMap: null, } - // When dynamicIO is enabled, missing data is encoded to an infinitely hanging - // promise, the absence of which we use to determine if a segment is fully - // static or partially static. However, when dynamicIO is not enabled, this - // trick doesn't work. - // - // So if PPR is enabled, and dynamicIO is not, we have to be conservative and - // assume all segments are partial. - // - // TODO: When PPR is on, we can at least optimize the case where the entire - // page is static. Either by passing that as an argument to this function, or - // by setting a header on the response like the we do for full page RSC - // prefetches today. The latter approach might be simpler since it requires - // less plumbing, and the client has to check the header regardless to see if - // PPR is enabled. - const shouldAssumePartialData = - renderOpts.experimental.isRoutePPREnabled === true && // PPR is enabled - !renderOpts.experimental.dynamicIO // dynamicIO is disabled - const staleTime = prerenderStore.stale return await ComponentMod.collectSegmentData( - shouldAssumePartialData, fullPageDataBuffer, staleTime, clientReferenceManifest.clientModules as ManifestNode, diff --git a/packages/next/src/server/app-render/collect-segment-data.tsx b/packages/next/src/server/app-render/collect-segment-data.tsx index 1ec7b2a07f25..d595b4d8799a 100644 --- a/packages/next/src/server/app-render/collect-segment-data.tsx +++ b/packages/next/src/server/app-render/collect-segment-data.tsx @@ -74,7 +74,6 @@ function onSegmentPrerenderError(error: unknown) { } export async function collectSegmentData( - shouldAssumePartialData: boolean, fullPageDataBuffer: Buffer, staleTime: number, clientModules: ManifestNode, @@ -119,7 +118,6 @@ export async function collectSegmentData( // inside of it, the side effects are transferred to the new stream. // @ts-expect-error - renderSegmentPrefetch( - shouldAssumePartialData, - buildId, - seedData, - key, - clientModules - ) + renderSegmentPrefetch(buildId, seedData, key, clientModules) ) ) } else { @@ -344,7 +330,6 @@ function encodeSegmentWithPossibleFallbackParam( } async function renderSegmentPrefetch( - shouldAssumePartialData: boolean, buildId: string, seedData: CacheNodeSeedData, key: string, @@ -359,8 +344,7 @@ async function renderSegmentPrefetch( buildId, rsc, loading, - isPartial: - shouldAssumePartialData || (await isPartialRSCData(rsc, clientModules)), + isPartial: await isPartialRSCData(rsc, clientModules), } // Since all we're doing is decoding and re-encoding a cached prerender, if // it takes longer than a microtask, it must because of hanging promises @@ -403,6 +387,11 @@ async function isPartialRSCData( await prerender(rsc, clientModules, { signal: abortController.signal, onError() {}, + onPostpone() { + // If something postponed, i.e. when Dynamic IO is not enabled, we can + // infer that the RSC data is partial. + isPartial = true + }, }) return isPartial } diff --git a/test/e2e/app-dir/segment-cache/client-only-opt-in/next.config.js b/test/e2e/app-dir/segment-cache/client-only-opt-in/next.config.js index 64a383e0b691..209191815e9e 100644 --- a/test/e2e/app-dir/segment-cache/client-only-opt-in/next.config.js +++ b/test/e2e/app-dir/segment-cache/client-only-opt-in/next.config.js @@ -4,7 +4,6 @@ const nextConfig = { experimental: { ppr: 'incremental', - dynamicIO: true, clientSegmentCache: 'client-only', }, } diff --git a/test/e2e/app-dir/segment-cache/export/next.config.js b/test/e2e/app-dir/segment-cache/export/next.config.js index e740e0c5233b..62ddfa93abf3 100644 --- a/test/e2e/app-dir/segment-cache/export/next.config.js +++ b/test/e2e/app-dir/segment-cache/export/next.config.js @@ -4,8 +4,6 @@ const nextConfig = { output: 'export', experimental: { - ppr: false, - dynamicIO: true, clientSegmentCache: true, }, } diff --git a/test/e2e/app-dir/segment-cache/incremental-opt-in/next.config.js b/test/e2e/app-dir/segment-cache/incremental-opt-in/next.config.js index ee74ac5cb97b..1fb210a73ee9 100644 --- a/test/e2e/app-dir/segment-cache/incremental-opt-in/next.config.js +++ b/test/e2e/app-dir/segment-cache/incremental-opt-in/next.config.js @@ -4,7 +4,6 @@ const nextConfig = { experimental: { ppr: 'incremental', - dynamicIO: true, clientSegmentCache: true, }, } diff --git a/test/ppr-tests-manifest.json b/test/ppr-tests-manifest.json index f0cf5b8e6b13..9a6cd1ab2df3 100644 --- a/test/ppr-tests-manifest.json +++ b/test/ppr-tests-manifest.json @@ -97,6 +97,7 @@ "test/e2e/app-dir/static-shell-debugging/static-shell-debugging.test.ts", "test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.prospective-fallback.test.ts", "test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts", + "test/e2e/app-dir/segment-cache/export/segment-cache-output-export.test.ts", "test/e2e/app-dir/segment-cache/incremental-opt-in/segment-cache-incremental-opt-in.test.ts", "test/e2e/app-dir/segment-cache/memory-pressure/segment-cache-memory-pressure.test.ts", "test/e2e/app-dir/segment-cache/prefetch-scheduling/prefetch-scheduling.test.ts", From 04e4f92ffcc8a1b0868b186dda35a24fb06eb339 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 16 May 2025 23:05:50 +0200 Subject: [PATCH 2/2] Add changeset --- .changeset/shaggy-pears-tell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shaggy-pears-tell.md diff --git a/.changeset/shaggy-pears-tell.md b/.changeset/shaggy-pears-tell.md new file mode 100644 index 000000000000..4cd6e97ad227 --- /dev/null +++ b/.changeset/shaggy-pears-tell.md @@ -0,0 +1,5 @@ +--- +"next": patch +--- + +Use `onPostpone` to determine if segment prefetch is partial