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