Skip to content

Commit e2837fb

Browse files
authored
Use onPostpone to determine if segment prefetch is partial (#79299)
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`. Without this change, the following tests would fail because additional requests for the PPR-enabled routes are triggered when `dynamicIO` is disabled: - `when a link is prefetched with <Link prefetch=true>, no dynamic request is made on navigation` - `when prefetching with prefetch=true, refetches cache entries that only contain partial data` - `when prefetching with prefetch=true, refetches partial cache entries even if there's already a pending PPR request` 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'`.
1 parent 3ecf087 commit e2837fb

File tree

7 files changed

+14
-42
lines changed

7 files changed

+14
-42
lines changed

.changeset/shaggy-pears-tell.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"next": patch
3+
---
4+
5+
Use `onPostpone` to determine if segment prefetch is partial

packages/next/src/server/app-render/app-render.tsx

-19
Original file line numberDiff line numberDiff line change
@@ -4258,27 +4258,8 @@ async function collectSegmentData(
42584258
serverModuleMap: null,
42594259
}
42604260

4261-
// When dynamicIO is enabled, missing data is encoded to an infinitely hanging
4262-
// promise, the absence of which we use to determine if a segment is fully
4263-
// static or partially static. However, when dynamicIO is not enabled, this
4264-
// trick doesn't work.
4265-
//
4266-
// So if PPR is enabled, and dynamicIO is not, we have to be conservative and
4267-
// assume all segments are partial.
4268-
//
4269-
// TODO: When PPR is on, we can at least optimize the case where the entire
4270-
// page is static. Either by passing that as an argument to this function, or
4271-
// by setting a header on the response like the we do for full page RSC
4272-
// prefetches today. The latter approach might be simpler since it requires
4273-
// less plumbing, and the client has to check the header regardless to see if
4274-
// PPR is enabled.
4275-
const shouldAssumePartialData =
4276-
renderOpts.experimental.isRoutePPREnabled === true && // PPR is enabled
4277-
!renderOpts.experimental.dynamicIO // dynamicIO is disabled
4278-
42794261
const staleTime = prerenderStore.stale
42804262
return await ComponentMod.collectSegmentData(
4281-
shouldAssumePartialData,
42824263
fullPageDataBuffer,
42834264
staleTime,
42844265
clientReferenceManifest.clientModules as ManifestNode,

packages/next/src/server/app-render/collect-segment-data.tsx

+8-19
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ function onSegmentPrerenderError(error: unknown) {
7474
}
7575

7676
export async function collectSegmentData(
77-
shouldAssumePartialData: boolean,
7877
fullPageDataBuffer: Buffer,
7978
staleTime: number,
8079
clientModules: ManifestNode,
@@ -119,7 +118,6 @@ export async function collectSegmentData(
119118
// inside of it, the side effects are transferred to the new stream.
120119
// @ts-expect-error
121120
<PrefetchTreeData
122-
shouldAssumePartialData={shouldAssumePartialData}
123121
fullPageDataBuffer={fullPageDataBuffer}
124122
fallbackRouteParams={fallbackRouteParams}
125123
serverConsumerManifest={serverConsumerManifest}
@@ -150,7 +148,6 @@ export async function collectSegmentData(
150148
}
151149

152150
async function PrefetchTreeData({
153-
shouldAssumePartialData,
154151
fullPageDataBuffer,
155152
fallbackRouteParams,
156153
serverConsumerManifest,
@@ -159,7 +156,6 @@ async function PrefetchTreeData({
159156
segmentTasks,
160157
onCompletedProcessingRouteTree,
161158
}: {
162-
shouldAssumePartialData: boolean
163159
fullPageDataBuffer: Buffer
164160
serverConsumerManifest: any
165161
fallbackRouteParams: FallbackRouteParams | null
@@ -199,7 +195,6 @@ async function PrefetchTreeData({
199195
// walk the tree, we will also spawn a task to produce a prefetch response for
200196
// each segment.
201197
const tree = collectSegmentDataImpl(
202-
shouldAssumePartialData,
203198
flightRouterState,
204199
buildId,
205200
seedData,
@@ -211,8 +206,7 @@ async function PrefetchTreeData({
211206
segmentTasks
212207
)
213208

214-
const isHeadPartial =
215-
shouldAssumePartialData || (await isPartialRSCData(head, clientModules))
209+
const isHeadPartial = await isPartialRSCData(head, clientModules)
216210

217211
// Notify the abort controller that we're done processing the route tree.
218212
// Anything async that happens after this point must be due to hanging
@@ -231,7 +225,6 @@ async function PrefetchTreeData({
231225
}
232226

233227
function collectSegmentDataImpl(
234-
shouldAssumePartialData: boolean,
235228
route: FlightRouterState,
236229
buildId: string,
237230
seedData: CacheNodeSeedData | null,
@@ -265,7 +258,6 @@ function collectSegmentDataImpl(
265258
: encodeSegment(childSegment)
266259
)
267260
const childTree = collectSegmentDataImpl(
268-
shouldAssumePartialData,
269261
childRoute,
270262
buildId,
271263
childSeedData,
@@ -288,13 +280,7 @@ function collectSegmentDataImpl(
288280
// Since we're already in the middle of a render, wait until after the
289281
// current task to escape the current rendering context.
290282
waitAtLeastOneReactRenderTask().then(() =>
291-
renderSegmentPrefetch(
292-
shouldAssumePartialData,
293-
buildId,
294-
seedData,
295-
key,
296-
clientModules
297-
)
283+
renderSegmentPrefetch(buildId, seedData, key, clientModules)
298284
)
299285
)
300286
} else {
@@ -344,7 +330,6 @@ function encodeSegmentWithPossibleFallbackParam(
344330
}
345331

346332
async function renderSegmentPrefetch(
347-
shouldAssumePartialData: boolean,
348333
buildId: string,
349334
seedData: CacheNodeSeedData,
350335
key: string,
@@ -359,8 +344,7 @@ async function renderSegmentPrefetch(
359344
buildId,
360345
rsc,
361346
loading,
362-
isPartial:
363-
shouldAssumePartialData || (await isPartialRSCData(rsc, clientModules)),
347+
isPartial: await isPartialRSCData(rsc, clientModules),
364348
}
365349
// Since all we're doing is decoding and re-encoding a cached prerender, if
366350
// it takes longer than a microtask, it must because of hanging promises
@@ -403,6 +387,11 @@ async function isPartialRSCData(
403387
await prerender(rsc, clientModules, {
404388
signal: abortController.signal,
405389
onError() {},
390+
onPostpone() {
391+
// If something postponed, i.e. when Dynamic IO is not enabled, we can
392+
// infer that the RSC data is partial.
393+
isPartial = true
394+
},
406395
})
407396
return isPartial
408397
}

test/e2e/app-dir/segment-cache/client-only-opt-in/next.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
const nextConfig = {
55
experimental: {
66
ppr: 'incremental',
7-
dynamicIO: true,
87
clientSegmentCache: 'client-only',
98
},
109
}

test/e2e/app-dir/segment-cache/export/next.config.js

-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
const nextConfig = {
55
output: 'export',
66
experimental: {
7-
ppr: false,
8-
dynamicIO: true,
97
clientSegmentCache: true,
108
},
119
}

test/e2e/app-dir/segment-cache/incremental-opt-in/next.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
const nextConfig = {
55
experimental: {
66
ppr: 'incremental',
7-
dynamicIO: true,
87
clientSegmentCache: true,
98
},
109
}

test/ppr-tests-manifest.json

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"test/e2e/app-dir/static-shell-debugging/static-shell-debugging.test.ts",
9898
"test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.prospective-fallback.test.ts",
9999
"test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts",
100+
"test/e2e/app-dir/segment-cache/export/segment-cache-output-export.test.ts",
100101
"test/e2e/app-dir/segment-cache/incremental-opt-in/segment-cache-incremental-opt-in.test.ts",
101102
"test/e2e/app-dir/segment-cache/memory-pressure/segment-cache-memory-pressure.test.ts",
102103
"test/e2e/app-dir/segment-cache/prefetch-scheduling/prefetch-scheduling.test.ts",

0 commit comments

Comments
 (0)