@@ -2275,13 +2275,28 @@ async function renderToStream(
2275
2275
const environmentName = ( ) =>
2276
2276
requestStore . prerenderPhase === true ? 'Prerender' : 'Server'
2277
2277
2278
+ const [ resolveValidation , validationOutlet ] = createValidationOutlet ( )
2279
+ const debugChannel = setReactDebugChannel && createDebugChannel ( )
2280
+
2281
+ if ( debugChannel ) {
2282
+ const [ readableSsr , readableBrowser ] =
2283
+ debugChannel . clientSide . readable . tee ( )
2284
+
2285
+ reactDebugStream = readableSsr
2286
+
2287
+ setReactDebugChannel (
2288
+ { readable : readableBrowser } ,
2289
+ htmlRequestId ,
2290
+ requestId
2291
+ )
2292
+ }
2293
+
2278
2294
if ( process . env . NEXT_RESTART_ON_CACHE_MISS !== '0' ) {
2279
2295
// Try to render the page and see if there's any cache misses.
2280
2296
// If there are, wait for caches to finish and restart the render.
2281
2297
2282
- const [ resolveValidation , validationOutlet ] = createValidationOutlet ( )
2283
-
2284
2298
const renderRestartable = async (
2299
+ serverDebugChannel : DebugChannelServer | undefined ,
2285
2300
signal : AbortSignal | undefined ,
2286
2301
onPrerenderStageEnd : ( ( ) => void ) | undefined
2287
2302
) => {
@@ -2305,8 +2320,7 @@ async function renderToStream(
2305
2320
onError : serverComponentsErrorHandler ,
2306
2321
environmentName,
2307
2322
filterStackFrame,
2308
- // TODO(restart-on-cache-miss): implement `debugChannel`
2309
- // debugChannel: debugChannel?.serverSide,
2323
+ debugChannel : serverDebugChannel ,
2310
2324
signal,
2311
2325
}
2312
2326
)
@@ -2331,12 +2345,18 @@ async function renderToStream(
2331
2345
requestStore . cacheSignal = cacheSignal
2332
2346
2333
2347
const initialRenderReactController = new AbortController ( )
2348
+ // We don't know if we'll use this render, so buffer debug channel writes until we find out.
2349
+ const initialRenderServerDebugChannel = debugChannel
2350
+ ? createBufferedServerDebugChannel ( )
2351
+ : undefined
2352
+
2334
2353
const hadCacheMissInStaticStagePromise =
2335
2354
createPromiseWithResolvers < boolean > ( )
2336
2355
2337
2356
console . debug ( `renderToStream (1) :: attempting render` )
2338
2357
2339
2358
const reactServerStreamPromise = renderRestartable (
2359
+ initialRenderServerDebugChannel ?. channel ,
2340
2360
initialRenderReactController . signal ,
2341
2361
( ) => {
2342
2362
console . debug (
@@ -2359,6 +2379,14 @@ async function renderToStream(
2359
2379
2360
2380
if ( ! hasCacheMissInStaticStage ) {
2361
2381
// No cache misses. Use the stream as is.
2382
+
2383
+ // The debug info from this render should be written to the real debug channel.
2384
+ if ( debugChannel && initialRenderServerDebugChannel ) {
2385
+ void initialRenderServerDebugChannel . pipeToChannel (
2386
+ debugChannel . serverSide
2387
+ )
2388
+ }
2389
+
2362
2390
reactServerResult = new ReactServerResult (
2363
2391
await reactServerStreamPromise
2364
2392
)
@@ -2391,14 +2419,23 @@ async function renderToStream(
2391
2419
)
2392
2420
requestStore . cacheSignal = null
2393
2421
2422
+ // We know we'll use this render, so unlike the initial one,
2423
+ // it can write into the debug channel directly instead of buffering.
2424
+ const finalRenderServerDebugChannel = debugChannel ?. serverSide
2425
+
2394
2426
reactServerResult = new ReactServerResult (
2395
- await renderRestartable ( undefined , ( ) => {
2396
- console . debug (
2397
- `renderToStream (2) :: end of static stage after restart. ${ cacheSignal [ 'count' ] } caches pending`
2398
- )
2399
- } )
2427
+ await renderRestartable (
2428
+ finalRenderServerDebugChannel ,
2429
+ undefined ,
2430
+ ( ) => {
2431
+ console . debug (
2432
+ `renderToStream (2) :: end of static stage after restart. ${ cacheSignal [ 'count' ] } caches pending`
2433
+ )
2434
+ }
2435
+ )
2400
2436
)
2401
2437
}
2438
+
2402
2439
// TODO(restart-on-cache-miss):
2403
2440
// This can probably be optimized to do less work,
2404
2441
// because we've already made sure that we have warm caches.
@@ -2415,25 +2452,8 @@ async function renderToStream(
2415
2452
)
2416
2453
} else {
2417
2454
const rscPayload = await getPayload ( )
2418
-
2419
- const [ resolveValidation , validationOutlet ] = createValidationOutlet ( )
2420
2455
rscPayload . _validation = validationOutlet
2421
2456
2422
- const debugChannel = setReactDebugChannel && createDebugChannel ( )
2423
-
2424
- if ( debugChannel ) {
2425
- const [ readableSsr , readableBrowser ] =
2426
- debugChannel . clientSide . readable . tee ( )
2427
-
2428
- reactDebugStream = readableSsr
2429
-
2430
- setReactDebugChannel (
2431
- { readable : readableBrowser } ,
2432
- htmlRequestId ,
2433
- requestId
2434
- )
2435
- }
2436
-
2437
2457
const reactServerStream = await workUnitAsyncStorage . run (
2438
2458
requestStore ,
2439
2459
scheduleInSequentialTasks ,
@@ -2822,12 +2842,21 @@ async function renderToStream(
2822
2842
}
2823
2843
}
2824
2844
2825
- function createDebugChannel ( ) :
2826
- | {
2827
- serverSide : { readable ?: ReadableStream ; writable : WritableStream }
2828
- clientSide : { readable : ReadableStream ; writable ?: WritableStream }
2829
- }
2830
- | undefined {
2845
+ type DebugChannelPair = {
2846
+ serverSide : DebugChannelServer
2847
+ clientSide : DebugChannelClient
2848
+ }
2849
+
2850
+ type DebugChannelServer = {
2851
+ readable ?: ReadableStream < Uint8Array >
2852
+ writable : WritableStream < Uint8Array >
2853
+ }
2854
+ type DebugChannelClient = {
2855
+ readable : ReadableStream < Uint8Array >
2856
+ writable ?: WritableStream < Uint8Array >
2857
+ }
2858
+
2859
+ function createDebugChannel ( ) : DebugChannelPair | undefined {
2831
2860
if ( process . env . NODE_ENV === 'production' ) {
2832
2861
return undefined
2833
2862
}
@@ -2860,6 +2889,41 @@ function createDebugChannel():
2860
2889
}
2861
2890
}
2862
2891
2892
+ function createBufferedServerDebugChannel ( ) {
2893
+ // We buffer all chunks until we're connected to a real debug channel using `connect()`.
2894
+ const chunks : Uint8Array [ ] = [ ]
2895
+ let onWrite = function bufferChunk ( chunk : Uint8Array ) {
2896
+ chunks . push ( chunk )
2897
+ }
2898
+ let onClose : ( ( ) => Promise < void > ) | undefined = undefined
2899
+
2900
+ const writable = new WritableStream < Uint8Array > ( {
2901
+ write ( chunk ) {
2902
+ onWrite ( chunk )
2903
+ } ,
2904
+ close ( ) {
2905
+ return onClose ?.( )
2906
+ } ,
2907
+ } )
2908
+
2909
+ return {
2910
+ channel : { writable } as DebugChannelServer ,
2911
+ /** Attach this stream to a real debug channel. */
2912
+ async pipeToChannel ( debugChannel : DebugChannelServer ) {
2913
+ // Once we're comitted to using this stream, write out the chunks we already have.
2914
+ const writer = debugChannel . writable . getWriter ( )
2915
+ for ( const chunk of chunks ) {
2916
+ await writer . write ( chunk )
2917
+ }
2918
+ chunks . length = 0
2919
+
2920
+ // After this point, we stop buffering, and future chunks will be written directly to the destination.
2921
+ onWrite = writer . write . bind ( writer )
2922
+ onClose = writer . close . bind ( writer )
2923
+ } ,
2924
+ }
2925
+ }
2926
+
2863
2927
function createValidationOutlet ( ) {
2864
2928
let resolveValidation : ( value : React . ReactNode ) => void
2865
2929
let outlet = new Promise < React . ReactNode > ( ( resolve ) => {
0 commit comments