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