@@ -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 { pipelineInSequentialTasks } from './app-render-render-utils'
175172import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler'
176173import {
177174 workUnitAsyncStorage ,
@@ -214,6 +211,7 @@ import type { Params } from '../request/params'
214211import { createPromiseWithResolvers } from '../../shared/lib/promise-with-resolvers'
215212import { ImageConfigContext } from '../../shared/lib/image-config-context.shared-runtime'
216213import { imageConfigDefault } from '../../shared/lib/image-config'
214+ import { RenderStage , StagedRenderingController } from './staged-rendering'
217215
218216export type GetDynamicParamFromSegment = (
219217 // [slug] / [[slug]] / [...slug]
@@ -2678,8 +2676,21 @@ async function renderWithRestartOnCacheMissInDev(
26782676 // If the render is restarted, we'll recreate a fresh request store
26792677 let requestStore : RequestStore = initialRequestStore
26802678
2681- const environmentName = ( ) =>
2682- requestStore . prerenderPhase === true ? 'Prerender' : 'Server'
2679+ const environmentName = ( ) => {
2680+ const currentStage = requestStore . stagedRendering ! . currentStage
2681+ switch ( currentStage ) {
2682+ case RenderStage . Static :
2683+ return 'Prerender'
2684+ case RenderStage . Runtime :
2685+ // TODO: only label as "Prefetch" if the page has a `prefetch` config.
2686+ return 'Prefetch'
2687+ case RenderStage . Dynamic :
2688+ return 'Server'
2689+ default :
2690+ currentStage satisfies never
2691+ throw new InvariantError ( `Invalid render stage: ${ currentStage } ` )
2692+ }
2693+ }
26832694
26842695 //===============================================
26852696 // Initial render
@@ -2699,14 +2710,19 @@ async function renderWithRestartOnCacheMissInDev(
26992710
27002711 const prerenderResumeDataCache = createPrerenderResumeDataCache ( )
27012712
2713+ const initialReactController = new AbortController ( )
2714+ const initialDataController = new AbortController ( ) // Controls hanging promises we create
2715+ const initialStageController = new StagedRenderingController (
2716+ initialDataController . signal
2717+ )
2718+
27022719 requestStore . prerenderResumeDataCache = prerenderResumeDataCache
27032720 // `getRenderResumeDataCache` will fall back to using `prerenderResumeDataCache` as `renderResumeDataCache`,
27042721 // so not having a resume data cache won't break any expectations in case we don't need to restart.
27052722 requestStore . renderResumeDataCache = null
2723+ requestStore . stagedRendering = initialStageController
27062724 requestStore . cacheSignal = cacheSignal
27072725
2708- const initialReactController = new AbortController ( )
2709-
27102726 let debugChannel = setReactDebugChannel && createDebugChannel ( )
27112727
27122728 const initialRscPayload = await getPayload ( requestStore )
@@ -2716,8 +2732,7 @@ async function renderWithRestartOnCacheMissInDev(
27162732 pipelineInSequentialTasks (
27172733 ( ) => {
27182734 // Static stage
2719- requestStore . prerenderPhase = true
2720- return ComponentMod . renderToReadableStream (
2735+ const stream = ComponentMod . renderToReadableStream (
27212736 initialRscPayload ,
27222737 clientReferenceManifest . clientModules ,
27232738 {
@@ -2728,25 +2743,42 @@ async function renderWithRestartOnCacheMissInDev(
27282743 signal : initialReactController . signal ,
27292744 }
27302745 )
2746+ // If we abort the render, we want to reject the stage-dependent promises as well.
2747+ // Note that we want to install this listener after the render is started
2748+ // so that it runs after react is finished running its abort code.
2749+ initialReactController . signal . addEventListener ( 'abort' , ( ) => {
2750+ initialDataController . abort ( initialReactController . signal . reason )
2751+ } )
2752+ return stream
2753+ } ,
2754+ ( stream ) => {
2755+ // Runtime stage
2756+ initialStageController . advanceStage ( RenderStage . Runtime )
2757+
2758+ // If we had a cache miss in the static stage, we'll have to disard this stream
2759+ // and render again once the caches are warm.
2760+ if ( cacheSignal . hasPendingReads ( ) ) {
2761+ return null
2762+ }
2763+
2764+ // If there's no cache misses, we'll continue rendering,
2765+ // and see if there's any cache misses in the runtime stage.
2766+ return stream
27312767 } ,
2732- async ( stream ) => {
2768+ async ( maybeStream ) => {
27332769 // Dynamic stage
2734- // Note: if we had cache misses, things that would've happened statically otherwise
2735- // may be marked as dynamic instead.
2736- requestStore . prerenderPhase = false
2737-
2738- // If all cache reads initiated in the static stage have completed,
2739- // then all of the necessary caches have to be warm (or there's no caches on the page).
2740- // On the other hand, if we still have pending cache reads, then we had a cache miss,
2741- // and the static stage didn't render all the content that it normally would have.
2742- const hadCacheMiss = cacheSignal . hasPendingReads ( )
2743- if ( ! hadCacheMiss ) {
2744- // No cache misses. We can use the stream as is.
2745- return stream
2746- } else {
2747- // Cache miss. We'll discard this stream, and render again.
2770+
2771+ // If we had cache misses in either of the previous stages,
2772+ // then we'll only use this render for filling caches.
2773+ // We won't advance the stage, and thus leave dynamic APIs hanging,
2774+ // because they won't be cached anyway, so it'd be wasted work.
2775+ if ( maybeStream === null || cacheSignal . hasPendingReads ( ) ) {
27482776 return null
27492777 }
2778+
2779+ // If there's no cache misses, we'll use this render, so let it advance to the dynamic stage.
2780+ initialStageController . advanceStage ( RenderStage . Dynamic )
2781+ return maybeStream
27502782 }
27512783 )
27522784 )
@@ -2779,40 +2811,48 @@ async function renderWithRestartOnCacheMissInDev(
27792811 // The initial render acted as a prospective render to warm the caches.
27802812 requestStore = createRequestStore ( )
27812813
2814+ const finalStageController = new StagedRenderingController ( )
2815+
27822816 // We've filled the caches, so now we can render as usual,
27832817 // without any cache-filling mechanics.
27842818 requestStore . prerenderResumeDataCache = null
27852819 requestStore . renderResumeDataCache = createRenderResumeDataCache (
27862820 prerenderResumeDataCache
27872821 )
2822+ requestStore . stagedRendering = finalStageController
27882823 requestStore . cacheSignal = null
27892824
27902825 // The initial render already wrote to its debug channel.
27912826 // We're not using it, so we need to create a new one.
27922827 debugChannel = setReactDebugChannel && createDebugChannel ( )
27932828
27942829 const finalRscPayload = await getPayload ( requestStore )
2795- const finalServerStream = await workUnitAsyncStorage . run (
2796- requestStore ,
2797- scheduleInSequentialTasks ,
2798- ( ) => {
2799- // Static stage
2800- requestStore . prerenderPhase = true
2801- return ComponentMod . renderToReadableStream (
2802- finalRscPayload ,
2803- clientReferenceManifest . clientModules ,
2804- {
2805- onError,
2806- environmentName,
2807- filterStackFrame,
2808- debugChannel : debugChannel ?. serverSide ,
2809- }
2810- )
2811- } ,
2812- ( ) => {
2813- // Dynamic stage
2814- requestStore . prerenderPhase = false
2815- }
2830+ const finalServerStream = await workUnitAsyncStorage . run ( requestStore , ( ) =>
2831+ pipelineInSequentialTasks (
2832+ ( ) => {
2833+ // Static stage
2834+ return ComponentMod . renderToReadableStream (
2835+ finalRscPayload ,
2836+ clientReferenceManifest . clientModules ,
2837+ {
2838+ onError,
2839+ environmentName,
2840+ filterStackFrame,
2841+ debugChannel : debugChannel ?. serverSide ,
2842+ }
2843+ )
2844+ } ,
2845+ ( stream ) => {
2846+ // Runtime stage
2847+ finalStageController . advanceStage ( RenderStage . Runtime )
2848+ return stream
2849+ } ,
2850+ ( stream ) => {
2851+ // Dynamic stage
2852+ finalStageController . advanceStage ( RenderStage . Dynamic )
2853+ return stream
2854+ }
2855+ )
28162856 )
28172857
28182858 return {
0 commit comments