From d44659744fd09736793c73e93ea79048f45570ea Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 10 Oct 2025 21:40:56 +0200 Subject: [PATCH 1/2] [Flight] Fix preload `as` attribute for stylesheets (#34760) Follow-up to #34604. For a stylesheet, we need to render ``, and not ``. ([ref](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload#what_types_of_content_can_be_preloaded)) fixes vercel/next.js#84569 --- .../react-dom-bindings/src/server/ReactFlightServerConfigDOM.js | 2 +- .../src/__tests__/ReactFlightDOM-test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js index 1c9b4763f5201..e5c1c22c190cf 100644 --- a/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js @@ -169,7 +169,7 @@ function processLink(props: Object, formatContext: FormatContext): void { return; } case 'stylesheet': { - preload(href, 'stylesheet', { + preload(href, 'style', { crossOrigin: props.crossOrigin, integrity: props.integrity, nonce: props.nonce, diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 9fe9c7aaf32fd..a2e08383264ac 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -2087,7 +2087,7 @@ describe('ReactFlightDOM', () => { media="(orientation: landscape)" /> - +

hello world

From ead92181bdbecc332a98daa808f341726dcaa414 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 10 Oct 2025 21:44:28 +0200 Subject: [PATCH 2/2] [Flight] Avoid unnecessary indirection when serializing debug info (#34797) When a debug channel is hooked up, and we're serializing debug models, if the result is an already outlined reference, we can emit it directly, without also outlining the reference. This would create an unnecessary indirection. Before: ``` :N1760023808330.2688 0:D"$2" 0:D"$3" 0:D"$4" 0:"hi" 1:{"name":"Component","key":null,"env":"Server","stack":[],"props":{}} 2:{"time":3.0989999999999327} 3:"$1" 4:{"time":3.261792000000014} ``` After: ``` :N1760023786873.8916 0:D"$2" 0:D"$1" 0:D"$3" 0:"hi" 1:{"name":"Component","key":null,"env":"Server","stack":[],"props":{}} 2:{"time":2.4145829999999933} 3:{"time":2.5488749999999527} ``` Notice how the second debug info chunk is now directly referencing chunk `1` in the debug channel, without outlining and referencing `"$1"` as its own debug chunk `3`. This not only simplifies the RSC payload, and reduces overhead. But more importantly it helps the client resolve cyclic references when a model has debug info that has a reference back to the model. The client is currently not able to resolve such a cycle when those chunk indirections are involved. Ideally, it would also be able to resolve them regardless, but that requires more work. In the meantime, this fixes an immediate issue. --- .../__tests__/ReactFlightDOMBrowser-test.js | 56 +++++++++++++++++++ .../react-server/src/ReactFlightServer.js | 23 +++++--- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index 9c7b5b62b195e..40ee397129863 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -3051,4 +3051,60 @@ describe('ReactFlightDOMBrowser', () => { `); } }); + + it('should resolve a cycle between debug info and the value it produces when using a debug channel', async () => { + // Same as `should resolve a cycle between debug info and the value it produces`, but using a debug channel. + + function Inner({style}) { + return
; + } + + function Component({style}) { + return ; + } + + const style = {}; + const element = ; + style.element = element; + + let debugReadableStreamController; + + const debugReadableStream = new ReadableStream({ + start(controller) { + debugReadableStreamController = controller; + }, + }); + + const rscStream = await serverAct(() => + ReactServerDOMServer.renderToReadableStream(element, webpackMap, { + debugChannel: { + writable: new WritableStream({ + write(chunk) { + debugReadableStreamController.enqueue(chunk); + }, + close() { + debugReadableStreamController.close(); + }, + }), + }, + }), + ); + + function ClientRoot({response}) { + return use(response); + } + + const response = ReactServerDOMClient.createFromReadableStream(rscStream, { + debugChannel: {readable: debugReadableStream}, + }); + + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render(); + }); + + expect(container.innerHTML).toBe('
'); + }); }); diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 546e1bbb64f08..152be2eecca57 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -4300,14 +4300,21 @@ function emitDebugChunk( const json: string = serializeDebugModel(request, 500, debugInfo); if (request.debugDestination !== null) { - // Outline the actual timing information to the debug channel. - const outlinedId = request.nextChunkId++; - const debugRow = outlinedId.toString(16) + ':' + json + '\n'; - request.pendingDebugChunks++; - request.completedDebugChunks.push(stringToChunk(debugRow)); - const row = - serializeRowHeader('D', id) + '"$' + outlinedId.toString(16) + '"\n'; - request.completedRegularChunks.push(stringToChunk(row)); + if (json[0] === '"' && json[1] === '$') { + // This is already an outlined reference so we can just emit it directly, + // without an unnecessary indirection. + const row = serializeRowHeader('D', id) + json + '\n'; + request.completedRegularChunks.push(stringToChunk(row)); + } else { + // Outline the debug information to the debug channel. + const outlinedId = request.nextChunkId++; + const debugRow = outlinedId.toString(16) + ':' + json + '\n'; + request.pendingDebugChunks++; + request.completedDebugChunks.push(stringToChunk(debugRow)); + const row = + serializeRowHeader('D', id) + '"$' + outlinedId.toString(16) + '"\n'; + request.completedRegularChunks.push(stringToChunk(row)); + } } else { const row = serializeRowHeader('D', id) + json + '\n'; request.completedRegularChunks.push(stringToChunk(row));