@@ -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]
@@ -2718,8 +2716,21 @@ async function renderWithRestartOnCacheMissInDev(
27182716 // If the render is restarted, we'll recreate a fresh request store
27192717 let requestStore : RequestStore = initialRequestStore
27202718
2721- const environmentName = ( ) =>
2722- requestStore . prerenderPhase === true ? 'Prerender' : 'Server'
2719+ const environmentName = ( ) => {
2720+ const currentStage = requestStore . stagedRendering ! . currentStage
2721+ switch ( currentStage ) {
2722+ case RenderStage . Static :
2723+ return 'Prerender'
2724+ case RenderStage . Runtime :
2725+ // TODO: only label as "Prefetch" if the page has a `prefetch` config.
2726+ return 'Prefetch'
2727+ case RenderStage . Dynamic :
2728+ return 'Server'
2729+ default :
2730+ currentStage satisfies never
2731+ throw new InvariantError ( `Invalid render stage: ${ currentStage } ` )
2732+ }
2733+ }
27232734
27242735 //===============================================
27252736 // Initial render
@@ -2739,14 +2750,19 @@ async function renderWithRestartOnCacheMissInDev(
27392750
27402751 const prerenderResumeDataCache = createPrerenderResumeDataCache ( )
27412752
2753+ const initialReactController = new AbortController ( )
2754+ const initialDataController = new AbortController ( ) // Controls hanging promises we create
2755+ const initialStageController = new StagedRenderingController (
2756+ initialDataController . signal
2757+ )
2758+
27422759 requestStore . prerenderResumeDataCache = prerenderResumeDataCache
27432760 // `getRenderResumeDataCache` will fall back to using `prerenderResumeDataCache` as `renderResumeDataCache`,
27442761 // so not having a resume data cache won't break any expectations in case we don't need to restart.
27452762 requestStore . renderResumeDataCache = null
2763+ requestStore . stagedRendering = initialStageController
27462764 requestStore . cacheSignal = cacheSignal
27472765
2748- const initialReactController = new AbortController ( )
2749-
27502766 let debugChannel = setReactDebugChannel && createDebugChannel ( )
27512767
27522768 const initialRscPayload = await getPayload ( requestStore )
@@ -2756,8 +2772,7 @@ async function renderWithRestartOnCacheMissInDev(
27562772 pipelineInSequentialTasks (
27572773 ( ) => {
27582774 // Static stage
2759- requestStore . prerenderPhase = true
2760- return ComponentMod . renderToReadableStream (
2775+ const stream = ComponentMod . renderToReadableStream (
27612776 initialRscPayload ,
27622777 clientReferenceManifest . clientModules ,
27632778 {
@@ -2768,25 +2783,42 @@ async function renderWithRestartOnCacheMissInDev(
27682783 signal : initialReactController . signal ,
27692784 }
27702785 )
2786+ // If we abort the render, we want to reject the stage-dependent promises as well.
2787+ // Note that we want to install this listener after the render is started
2788+ // so that it runs after react is finished running its abort code.
2789+ initialReactController . signal . addEventListener ( 'abort' , ( ) => {
2790+ initialDataController . abort ( initialReactController . signal . reason )
2791+ } )
2792+ return stream
2793+ } ,
2794+ ( stream ) => {
2795+ // Runtime stage
2796+ initialStageController . advanceStage ( RenderStage . Runtime )
2797+
2798+ // If we had a cache miss in the static stage, we'll have to disard this stream
2799+ // and render again once the caches are warm.
2800+ if ( cacheSignal . hasPendingReads ( ) ) {
2801+ return null
2802+ }
2803+
2804+ // If there's no cache misses, we'll continue rendering,
2805+ // and see if there's any cache misses in the runtime stage.
2806+ return stream
27712807 } ,
2772- async ( stream ) => {
2808+ async ( maybeStream ) => {
27732809 // Dynamic stage
2774- // Note: if we had cache misses, things that would've happened statically otherwise
2775- // may be marked as dynamic instead.
2776- requestStore . prerenderPhase = false
2777-
2778- // If all cache reads initiated in the static stage have completed,
2779- // then all of the necessary caches have to be warm (or there's no caches on the page).
2780- // On the other hand, if we still have pending cache reads, then we had a cache miss,
2781- // and the static stage didn't render all the content that it normally would have.
2782- const hadCacheMiss = cacheSignal . hasPendingReads ( )
2783- if ( ! hadCacheMiss ) {
2784- // No cache misses. We can use the stream as is.
2785- return stream
2786- } else {
2787- // Cache miss. We'll discard this stream, and render again.
2810+
2811+ // If we had cache misses in either of the previous stages,
2812+ // then we'll only use this render for filling caches.
2813+ // We won't advance the stage, and thus leave dynamic APIs hanging,
2814+ // because they won't be cached anyway, so it'd be wasted work.
2815+ if ( maybeStream === null || cacheSignal . hasPendingReads ( ) ) {
27882816 return null
27892817 }
2818+
2819+ // If there's no cache misses, we'll use this render, so let it advance to the dynamic stage.
2820+ initialStageController . advanceStage ( RenderStage . Dynamic )
2821+ return maybeStream
27902822 }
27912823 )
27922824 )
@@ -2819,40 +2851,48 @@ async function renderWithRestartOnCacheMissInDev(
28192851 // The initial render acted as a prospective render to warm the caches.
28202852 requestStore = createRequestStore ( )
28212853
2854+ const finalStageController = new StagedRenderingController ( )
2855+
28222856 // We've filled the caches, so now we can render as usual,
28232857 // without any cache-filling mechanics.
28242858 requestStore . prerenderResumeDataCache = null
28252859 requestStore . renderResumeDataCache = createRenderResumeDataCache (
28262860 prerenderResumeDataCache
28272861 )
2862+ requestStore . stagedRendering = finalStageController
28282863 requestStore . cacheSignal = null
28292864
28302865 // The initial render already wrote to its debug channel.
28312866 // We're not using it, so we need to create a new one.
28322867 debugChannel = setReactDebugChannel && createDebugChannel ( )
28332868
28342869 const finalRscPayload = await getPayload ( requestStore )
2835- const finalServerStream = await workUnitAsyncStorage . run (
2836- requestStore ,
2837- scheduleInSequentialTasks ,
2838- ( ) => {
2839- // Static stage
2840- requestStore . prerenderPhase = true
2841- return ComponentMod . renderToReadableStream (
2842- finalRscPayload ,
2843- clientReferenceManifest . clientModules ,
2844- {
2845- onError,
2846- environmentName,
2847- filterStackFrame,
2848- debugChannel : debugChannel ?. serverSide ,
2849- }
2850- )
2851- } ,
2852- ( ) => {
2853- // Dynamic stage
2854- requestStore . prerenderPhase = false
2855- }
2870+ const finalServerStream = await workUnitAsyncStorage . run ( requestStore , ( ) =>
2871+ pipelineInSequentialTasks (
2872+ ( ) => {
2873+ // Static stage
2874+ return ComponentMod . renderToReadableStream (
2875+ finalRscPayload ,
2876+ clientReferenceManifest . clientModules ,
2877+ {
2878+ onError,
2879+ environmentName,
2880+ filterStackFrame,
2881+ debugChannel : debugChannel ?. serverSide ,
2882+ }
2883+ )
2884+ } ,
2885+ ( stream ) => {
2886+ // Runtime stage
2887+ finalStageController . advanceStage ( RenderStage . Runtime )
2888+ return stream
2889+ } ,
2890+ ( stream ) => {
2891+ // Dynamic stage
2892+ finalStageController . advanceStage ( RenderStage . Dynamic )
2893+ return stream
2894+ }
2895+ )
28562896 )
28572897
28582898 return {
0 commit comments