@@ -168,10 +168,7 @@ import {
168
168
prerenderAndAbortInSequentialTasks ,
169
169
} from './app-render-prerender-utils'
170
170
import { printDebugThrownValueForProspectiveRender } from './prospective-render-utils'
171
- import {
172
- pipelineInSequentialTasks ,
173
- scheduleInSequentialTasks ,
174
- } from './app-render-render-utils'
171
+ import { pipelineInSequentialTasks3 } from './app-render-render-utils'
175
172
import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler'
176
173
import {
177
174
workUnitAsyncStorage ,
@@ -212,6 +209,7 @@ import {
212
209
import type { ExperimentalConfig } from '../config-shared'
213
210
import type { Params } from '../request/params'
214
211
import { createPromiseWithResolvers } from '../../shared/lib/promise-with-resolvers'
212
+ import { RenderStage , StagedRenderingController } from './staged-rendering'
215
213
216
214
export type GetDynamicParamFromSegment = (
217
215
// [slug] / [[slug]] / [...slug]
@@ -2193,8 +2191,21 @@ async function renderToStream(
2193
2191
)
2194
2192
}
2195
2193
2196
- const environmentName = ( ) =>
2197
- requestStore . prerenderPhase === true ? 'Prerender' : 'Server'
2194
+ const environmentName = ( ) => {
2195
+ const currentStage = requestStore . stagedRendering ! . currentStage
2196
+ switch ( currentStage ) {
2197
+ case RenderStage . Static :
2198
+ return 'Prerender'
2199
+ case RenderStage . Runtime :
2200
+ // TODO: only label as "Prefetch" if the page has a `prefetch` config.
2201
+ return 'Prefetch'
2202
+ case RenderStage . Dynamic :
2203
+ return 'Server'
2204
+ default :
2205
+ currentStage satisfies never
2206
+ throw new InvariantError ( `Invalid render stage: ${ currentStage } ` )
2207
+ }
2208
+ }
2198
2209
2199
2210
// Try to render the page and see if there's any cache misses.
2200
2211
// If there are, wait for caches to finish and restart the render.
@@ -2210,26 +2221,30 @@ async function renderToStream(
2210
2221
2211
2222
const prerenderResumeDataCache = createPrerenderResumeDataCache ( )
2212
2223
2224
+ const initialRenderReactController = new AbortController ( ) // Controls the react render
2225
+ const initialRenderDataController = new AbortController ( ) // Controls hanging promises we create
2226
+ const initialRenderStageController = new StagedRenderingController (
2227
+ initialRenderDataController . signal
2228
+ )
2229
+
2213
2230
requestStore . prerenderResumeDataCache = prerenderResumeDataCache
2214
2231
// `getRenderResumeDataCache` will fall back to using `prerenderResumeDataCache` as `renderResumeDataCache`,
2215
2232
// so not having a resume data cache won't break any expectations in case we don't need to restart.
2216
2233
requestStore . renderResumeDataCache = null
2234
+ requestStore . stagedRendering = initialRenderStageController
2217
2235
requestStore . cacheSignal = cacheSignal
2218
2236
2219
- const initialRenderReactController = new AbortController ( )
2220
-
2221
2237
const intialRenderDebugChannel =
2222
2238
setReactDebugChannel && createDebugChannel ( )
2223
2239
2224
2240
const initialRscPayload = await getPayload ( )
2225
2241
const maybeInitialServerStream = await workUnitAsyncStorage . run (
2226
2242
requestStore ,
2227
2243
( ) =>
2228
- pipelineInSequentialTasks (
2244
+ pipelineInSequentialTasks3 (
2229
2245
( ) => {
2230
2246
// Static stage
2231
- requestStore . prerenderPhase = true
2232
- return ComponentMod . renderToReadableStream (
2247
+ const stream = ComponentMod . renderToReadableStream (
2233
2248
initialRscPayload ,
2234
2249
clientReferenceManifest . clientModules ,
2235
2250
{
@@ -2240,25 +2255,57 @@ async function renderToStream(
2240
2255
signal : initialRenderReactController . signal ,
2241
2256
}
2242
2257
)
2258
+ // If we abort the render, we want to reject the stage-dependent promises as well.
2259
+ // Note that we want to install this listener after the render is started
2260
+ // so that it runs after react is finished running its abort code.
2261
+ initialRenderReactController . signal . addEventListener (
2262
+ 'abort' ,
2263
+ ( ) => {
2264
+ initialRenderDataController . abort (
2265
+ initialRenderReactController . signal . reason
2266
+ )
2267
+ }
2268
+ )
2269
+ return stream
2243
2270
} ,
2244
- async ( stream ) => {
2245
- // Dynamic stage
2246
- // Note: if we had cache misses, things that would've happened statically otherwise
2247
- // may be marked as dynamic instead.
2248
- requestStore . prerenderPhase = false
2271
+ ( stream ) => {
2272
+ // Runtime stage
2273
+ initialRenderStageController . advanceStage ( RenderStage . Runtime )
2249
2274
2250
2275
// If all cache reads initiated in the static stage have completed,
2251
2276
// then all of the necessary caches have to be warm (or there's no caches on the page).
2252
2277
// On the other hand, if we still have pending cache reads, then we had a cache miss,
2253
2278
// and the static stage didn't render all the content that it normally would have.
2254
- const hadCacheMiss = cacheSignal . hasPendingReads ( )
2255
- if ( ! hadCacheMiss ) {
2279
+ if ( ! cacheSignal . hasPendingReads ( ) ) {
2256
2280
// No cache misses. We can use the stream as is.
2257
2281
return stream
2258
2282
} else {
2259
2283
// Cache miss. We'll discard this stream, and render again.
2260
2284
return null
2261
2285
}
2286
+ } ,
2287
+ async ( maybeStream ) => {
2288
+ // Dynamic stage
2289
+
2290
+ if ( maybeStream === null ) {
2291
+ // If we had cache misses in either of the previous stages, then we'll only use this render for filling caches.
2292
+ // We won't advance the stage, and thus leave dynamic APIs hanging,
2293
+ // because they won't be cached anyway, so it'd be wasted work.
2294
+ return null
2295
+ }
2296
+
2297
+ // Note: if we had cache misses, things that would've happened statically otherwise
2298
+ // may be marked as dynamic instead.
2299
+ initialRenderStageController . advanceStage ( RenderStage . Dynamic )
2300
+
2301
+ // Analogous to the previous stage.
2302
+ if ( ! cacheSignal . hasPendingReads ( ) ) {
2303
+ // No cache misses. We can use the stream as is.
2304
+ return maybeStream
2305
+ } else {
2306
+ // Cache miss. We'll discard this stream, and render again.
2307
+ return null
2308
+ }
2262
2309
}
2263
2310
)
2264
2311
)
@@ -2291,11 +2338,14 @@ async function renderToStream(
2291
2338
// Now, we need to do another render.
2292
2339
requestStore = createRequestStore ( )
2293
2340
2341
+ const finalRenderStageController = new StagedRenderingController ( )
2342
+
2294
2343
// We've filled the caches, so now we can render as usual.
2295
2344
requestStore . prerenderResumeDataCache = null
2296
2345
requestStore . renderResumeDataCache = createRenderResumeDataCache (
2297
2346
prerenderResumeDataCache
2298
2347
)
2348
+ requestStore . stagedRendering = finalRenderStageController
2299
2349
requestStore . cacheSignal = null
2300
2350
2301
2351
// The initial render already wrote to its debug channel. We're not using it,
@@ -2310,25 +2360,32 @@ async function renderToStream(
2310
2360
const finalRscPayload = await getPayload ( )
2311
2361
const finalServerStream = await workUnitAsyncStorage . run (
2312
2362
requestStore ,
2313
- scheduleInSequentialTasks ,
2314
- ( ) => {
2315
- // Static stage
2316
- requestStore . prerenderPhase = true
2317
- return ComponentMod . renderToReadableStream (
2318
- finalRscPayload ,
2319
- clientReferenceManifest . clientModules ,
2320
- {
2321
- onError : serverComponentsErrorHandler ,
2322
- environmentName,
2323
- filterStackFrame,
2324
- debugChannel : finalRenderDebugChannel ?. serverSide ,
2363
+ ( ) =>
2364
+ pipelineInSequentialTasks3 (
2365
+ ( ) => {
2366
+ // Static stage
2367
+ return ComponentMod . renderToReadableStream (
2368
+ finalRscPayload ,
2369
+ clientReferenceManifest . clientModules ,
2370
+ {
2371
+ onError : serverComponentsErrorHandler ,
2372
+ environmentName,
2373
+ filterStackFrame,
2374
+ debugChannel : finalRenderDebugChannel ?. serverSide ,
2375
+ }
2376
+ )
2377
+ } ,
2378
+ ( stream ) => {
2379
+ // Runtime stage
2380
+ finalRenderStageController . advanceStage ( RenderStage . Runtime )
2381
+ return stream
2382
+ } ,
2383
+ ( stream ) => {
2384
+ // Dynamic stage
2385
+ finalRenderStageController . advanceStage ( RenderStage . Dynamic )
2386
+ return stream
2325
2387
}
2326
2388
)
2327
- } ,
2328
- ( ) => {
2329
- // Dynamic stage
2330
- requestStore . prerenderPhase = false
2331
- }
2332
2389
)
2333
2390
2334
2391
reactServerResult = new ReactServerResult ( finalServerStream )
0 commit comments