@@ -168,7 +168,10 @@ import {
168
168
prerenderAndAbortInSequentialTasks ,
169
169
} from './app-render-prerender-utils'
170
170
import { printDebugThrownValueForProspectiveRender } from './prospective-render-utils'
171
- import { scheduleInSequentialTasks } from './app-render-render-utils'
171
+ import {
172
+ pipelineInSequentialTasks ,
173
+ scheduleInSequentialTasks ,
174
+ } from './app-render-render-utils'
172
175
import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler'
173
176
import {
174
177
workUnitAsyncStorage ,
@@ -2128,19 +2131,28 @@ async function renderToStream(
2128
2131
_validation ?: Promise < React . ReactNode >
2129
2132
}
2130
2133
2131
- const getPayload = ( ) : Promise < RSCPayloadWithValidation > =>
2132
- workUnitAsyncStorage . run (
2133
- requestStore ,
2134
- getRSCPayload ,
2135
- tree ,
2136
- ctx ,
2137
- res . statusCode === 404
2138
- )
2134
+ const [ resolveValidation , validationOutlet ] = createValidationOutlet ( )
2135
+
2136
+ const getPayload = async ( ) : Promise < RSCPayloadWithValidation > => {
2137
+ const payload : RSCPayloadWithValidation =
2138
+ await workUnitAsyncStorage . run (
2139
+ requestStore ,
2140
+ getRSCPayload ,
2141
+ tree ,
2142
+ ctx ,
2143
+ res . statusCode === 404
2144
+ )
2145
+ // Placing the validation outlet in the payload is safe
2146
+ // even if we end up discarding a render and restarting,
2147
+ // because we're not going to wait for the stream to complete,
2148
+ // so leaving the validation unresolved is fine.
2149
+ payload . _validation = validationOutlet
2150
+ return payload
2151
+ }
2139
2152
2140
2153
const environmentName = ( ) =>
2141
2154
requestStore . prerenderPhase === true ? 'Prerender' : 'Server'
2142
2155
2143
- const [ resolveValidation , validationOutlet ] = createValidationOutlet ( )
2144
2156
const debugChannel = setReactDebugChannel && createDebugChannel ( )
2145
2157
2146
2158
if ( debugChannel ) {
@@ -2159,44 +2171,6 @@ async function renderToStream(
2159
2171
// Try to render the page and see if there's any cache misses.
2160
2172
// If there are, wait for caches to finish and restart the render.
2161
2173
2162
- const renderInStages = async (
2163
- serverDebugChannel : DebugChannelServer | undefined ,
2164
- signal : AbortSignal | undefined ,
2165
- onPrerenderStageEnd : ( ( ) => void ) | undefined
2166
- ) => {
2167
- const rscPayload = await getPayload ( )
2168
-
2169
- // Placing the validation outlet in the payload is safe
2170
- // even if we end up discarding this render and restarting,
2171
- // because it's just an output produced independently.
2172
- rscPayload . _validation = validationOutlet
2173
-
2174
- return workUnitAsyncStorage . run (
2175
- requestStore ,
2176
- scheduleInSequentialTasks ,
2177
- ( ) => {
2178
- // Static stage
2179
- requestStore . prerenderPhase = true
2180
- return ComponentMod . renderToReadableStream (
2181
- rscPayload ,
2182
- clientReferenceManifest . clientModules ,
2183
- {
2184
- onError : serverComponentsErrorHandler ,
2185
- environmentName,
2186
- filterStackFrame,
2187
- debugChannel : serverDebugChannel ,
2188
- signal,
2189
- }
2190
- )
2191
- } ,
2192
- ( ) => {
2193
- // Dynamic stage
2194
- requestStore . prerenderPhase = false
2195
- onPrerenderStageEnd ?.( )
2196
- }
2197
- )
2198
- }
2199
-
2200
2174
// This render might end up being used as a prospective render (if there's cache misses),
2201
2175
// so we need to set it up for filling caches.
2202
2176
const cacheSignal = new CacheSignal ( )
@@ -2216,45 +2190,61 @@ async function renderToStream(
2216
2190
2217
2191
const initialRenderReactController = new AbortController ( )
2218
2192
// We don't know if we'll use this render, so buffer debug channel writes until we find out.
2219
- const initialRenderServerDebugChannel = debugChannel
2193
+ const bufferedServerDebugChannel = debugChannel
2220
2194
? createBufferedServerDebugChannel ( )
2221
2195
: undefined
2222
2196
2223
- const hadCacheMissInStaticStagePromise =
2224
- createPromiseWithResolvers < boolean > ( )
2225
-
2226
- const reactServerStreamPromise = renderInStages (
2227
- initialRenderServerDebugChannel ?. channel ,
2228
- initialRenderReactController . signal ,
2229
- ( ) => {
2230
- // If all cache reads initiated in the static stage have completed,
2231
- // then either we don't need to fill any caches, or all of them are warm.
2232
- // On the other hand, if we have pending cache reads, then we had a cache miss.
2233
- hadCacheMissInStaticStagePromise . resolve (
2234
- cacheSignal . hasPendingReads ( )
2197
+ const initialRscPayload = await getPayload ( )
2198
+ const maybeInitialServerStream = await workUnitAsyncStorage . run (
2199
+ requestStore ,
2200
+ ( ) =>
2201
+ pipelineInSequentialTasks (
2202
+ ( ) => {
2203
+ // Static stage
2204
+ requestStore . prerenderPhase = true
2205
+ return ComponentMod . renderToReadableStream (
2206
+ initialRscPayload ,
2207
+ clientReferenceManifest . clientModules ,
2208
+ {
2209
+ onError : serverComponentsErrorHandler ,
2210
+ environmentName,
2211
+ filterStackFrame,
2212
+ debugChannel : bufferedServerDebugChannel ?. channel ,
2213
+ signal : initialRenderReactController . signal ,
2214
+ }
2215
+ )
2216
+ } ,
2217
+ async ( stream ) => {
2218
+ // Dynamic stage
2219
+ // Note: if we had cache misses, things that would've happened statically otherwise
2220
+ // may be marked as dynamic instead.
2221
+ requestStore . prerenderPhase = false
2222
+
2223
+ // If all cache reads initiated in the static stage have completed,
2224
+ // then all of the necessary caches have to be warm (or there's no caches on the page).
2225
+ // On the other hand, if we still have pending cache reads, then we had a cache miss,
2226
+ // and the static stage didn't render all the content that it normally would have.
2227
+ const hadCacheMiss = cacheSignal . hasPendingReads ( )
2228
+ if ( ! hadCacheMiss ) {
2229
+ // No cache misses. We can use the stream as is.
2230
+ return stream
2231
+ } else {
2232
+ // Cache miss. We'll discard this stream, and render again.
2233
+ return null
2234
+ }
2235
+ }
2235
2236
)
2236
- }
2237
- )
2238
- reactServerStreamPromise . catch ( ( err ) =>
2239
- hadCacheMissInStaticStagePromise . reject ( err )
2240
2237
)
2241
2238
2242
- const hasCacheMissInStaticStage =
2243
- await hadCacheMissInStaticStagePromise . promise
2244
-
2245
- if ( ! hasCacheMissInStaticStage ) {
2246
- // No cache misses. Use the stream as is.
2239
+ if ( maybeInitialServerStream !== null ) {
2240
+ // No cache misses. We can use the stream as is.
2247
2241
2248
- // The debug info from this render should be written to the real debug channel.
2249
- if ( debugChannel && initialRenderServerDebugChannel ) {
2250
- void initialRenderServerDebugChannel . pipeToChannel (
2251
- debugChannel . serverSide
2252
- )
2242
+ // Since we're using this render, the debug info we've buffered should be written to the real debug channel.
2243
+ if ( debugChannel && bufferedServerDebugChannel ) {
2244
+ void bufferedServerDebugChannel . pipeToChannel ( debugChannel . serverSide )
2253
2245
}
2254
2246
2255
- reactServerResult = new ReactServerResult (
2256
- await reactServerStreamPromise
2257
- )
2247
+ reactServerResult = new ReactServerResult ( maybeInitialServerStream )
2258
2248
} else {
2259
2249
// Cache miss. We will use the initial render to fill caches, and discard its result.
2260
2250
// Then, we can render again with warm caches.
@@ -2272,25 +2262,51 @@ async function renderToStream(
2272
2262
await cacheSignal . cacheReadyInRender ( )
2273
2263
initialRenderReactController . abort ( )
2274
2264
2275
- // The initial render acted as a prospective render.
2276
- // Now, we need to clear the state we've set up for it and do a regular render.
2265
+ //===============================================
2266
+
2267
+ // The initial render acted as a prospective render to warm the caches.
2268
+ // Now, we need to do another render.
2269
+
2270
+ // TODO(restart-on-cache-miss): we should use a separate request store for this instead
2271
+
2272
+ // We've filled the caches, so now we can render as usual.
2277
2273
requestStore . prerenderResumeDataCache = null
2278
2274
requestStore . renderResumeDataCache = createRenderResumeDataCache (
2279
2275
prerenderResumeDataCache
2280
2276
)
2281
2277
requestStore . cacheSignal = null
2282
2278
2283
- // We know we'll use this render, so unlike the initial one,
2284
- // it can write into the debug channel directly instead of buffering.
2285
- const finalRenderServerDebugChannel = debugChannel ?. serverSide
2279
+ // Reset mutable fields.
2280
+ requestStore . prerenderPhase = undefined
2281
+ requestStore . usedDynamic = undefined
2286
2282
2287
- reactServerResult = new ReactServerResult (
2288
- await renderInStages (
2289
- finalRenderServerDebugChannel ,
2290
- undefined ,
2291
- undefined
2292
- )
2283
+ const finalRscPayload = await getPayload ( )
2284
+ const finalServerStream = await workUnitAsyncStorage . run (
2285
+ requestStore ,
2286
+ scheduleInSequentialTasks ,
2287
+ ( ) => {
2288
+ // Static stage
2289
+ requestStore . prerenderPhase = true
2290
+ return ComponentMod . renderToReadableStream (
2291
+ finalRscPayload ,
2292
+ clientReferenceManifest . clientModules ,
2293
+ {
2294
+ onError : serverComponentsErrorHandler ,
2295
+ environmentName,
2296
+ filterStackFrame,
2297
+ // We know we'll use this render, so unlike the initial one,
2298
+ // it can write into the debug channel directly instead of buffering.
2299
+ debugChannel : debugChannel ?. serverSide ,
2300
+ }
2301
+ )
2302
+ } ,
2303
+ ( ) => {
2304
+ // Dynamic stage
2305
+ requestStore . prerenderPhase = false
2306
+ }
2293
2307
)
2308
+
2309
+ reactServerResult = new ReactServerResult ( finalServerStream )
2294
2310
}
2295
2311
2296
2312
// TODO(restart-on-cache-miss):
0 commit comments