@@ -168,10 +168,7 @@ import {
168168 prerenderAndAbortInSequentialTasks ,
169169} from './app-render-prerender-utils'
170170import { 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'
175172import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler'
176173import {
177174 workUnitAsyncStorage ,
@@ -212,6 +209,7 @@ import {
212209import type { ExperimentalConfig } from '../config-shared'
213210import type { Params } from '../request/params'
214211import { createPromiseWithResolvers } from '../../shared/lib/promise-with-resolvers'
212+ import { RenderStage , StagedRenderingController } from './staged-rendering'
215213
216214export type GetDynamicParamFromSegment = (
217215 // [slug] / [[slug]] / [...slug]
@@ -2203,8 +2201,21 @@ async function renderToStream(
22032201 )
22042202 }
22052203
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+ }
22082219
22092220 // Try to render the page and see if there's any cache misses.
22102221 // If there are, wait for caches to finish and restart the render.
@@ -2220,26 +2231,30 @@ async function renderToStream(
22202231
22212232 const prerenderResumeDataCache = createPrerenderResumeDataCache ( )
22222233
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+
22232240 requestStore . prerenderResumeDataCache = prerenderResumeDataCache
22242241 // `getRenderResumeDataCache` will fall back to using `prerenderResumeDataCache` as `renderResumeDataCache`,
22252242 // so not having a resume data cache won't break any expectations in case we don't need to restart.
22262243 requestStore . renderResumeDataCache = null
2244+ requestStore . stagedRendering = initialRenderStageController
22272245 requestStore . cacheSignal = cacheSignal
22282246
2229- const initialRenderReactController = new AbortController ( )
2230-
22312247 const intialRenderDebugChannel =
22322248 setReactDebugChannel && createDebugChannel ( )
22332249
22342250 const initialRscPayload = await getPayload ( )
22352251 const maybeInitialServerStream = await workUnitAsyncStorage . run (
22362252 requestStore ,
22372253 ( ) =>
2238- pipelineInSequentialTasks (
2254+ pipelineInSequentialTasks3 (
22392255 ( ) => {
22402256 // Static stage
2241- requestStore . prerenderPhase = true
2242- return ComponentMod . renderToReadableStream (
2257+ const stream = ComponentMod . renderToReadableStream (
22432258 initialRscPayload ,
22442259 clientReferenceManifest . clientModules ,
22452260 {
@@ -2250,25 +2265,57 @@ async function renderToStream(
22502265 signal : initialRenderReactController . signal ,
22512266 }
22522267 )
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
22532280 } ,
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 )
22592284
22602285 // If all cache reads initiated in the static stage have completed,
22612286 // then all of the necessary caches have to be warm (or there's no caches on the page).
22622287 // On the other hand, if we still have pending cache reads, then we had a cache miss,
22632288 // 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 ( ) ) {
22662290 // No cache misses. We can use the stream as is.
22672291 return stream
22682292 } else {
22692293 // Cache miss. We'll discard this stream, and render again.
22702294 return null
22712295 }
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+ }
22722319 }
22732320 )
22742321 )
@@ -2301,11 +2348,14 @@ async function renderToStream(
23012348 // Now, we need to do another render.
23022349 requestStore = createRequestStore ( )
23032350
2351+ const finalRenderStageController = new StagedRenderingController ( )
2352+
23042353 // We've filled the caches, so now we can render as usual.
23052354 requestStore . prerenderResumeDataCache = null
23062355 requestStore . renderResumeDataCache = createRenderResumeDataCache (
23072356 prerenderResumeDataCache
23082357 )
2358+ requestStore . stagedRendering = finalRenderStageController
23092359 requestStore . cacheSignal = null
23102360
23112361 // The initial render already wrote to its debug channel. We're not using it,
@@ -2320,25 +2370,32 @@ async function renderToStream(
23202370 const finalRscPayload = await getPayload ( )
23212371 const finalServerStream = await workUnitAsyncStorage . run (
23222372 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
23352397 }
23362398 )
2337- } ,
2338- ( ) => {
2339- // Dynamic stage
2340- requestStore . prerenderPhase = false
2341- }
23422399 )
23432400
23442401 reactServerResult = new ReactServerResult ( finalServerStream )
0 commit comments