From f110a3735b9c4cbdc23b302592f5c24e6a666f02 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 21 Feb 2022 13:40:59 +0100 Subject: [PATCH] Fix rsc bootstrap buffer missing in the future renders (#34631) ## Bug If there's upcoming streaming data from server components, should safely skip the bootstrap process. Previously we deleted the buffer then it will cause the buffer is missing in the later re-renders. Now we mark it as empty array, so it can safely skip the boostrap phase x-ref: #34475 - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [x] Errors have helpful link attached, see `contributing.md` --- packages/next/client/index.tsx | 4 +++- .../app/next.config.js | 1 + .../app/pages/streaming-rsc.server.js | 23 +++++++++++++++++++ .../test/rsc.js | 6 +++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/integration/react-streaming-and-server-components/app/pages/streaming-rsc.server.js diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 08fdab14c86a070..ac8b5aa38195f46 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -702,7 +702,9 @@ if (process.env.__NEXT_RSC) { writer.write(encoder.encode(val)) }) buffer.length = 0 - serverDataBuffer.delete(key) + // Clean buffer but not deleting the key to mark bootstrap as complete. + // Then `nextServerDataCallback` will be safely skipped in the future renders. + serverDataBuffer.set(key, []) } serverDataWriter.set(key, writer) } diff --git a/test/integration/react-streaming-and-server-components/app/next.config.js b/test/integration/react-streaming-and-server-components/app/next.config.js index add280d239bb309..a17299bc8c9eaf2 100644 --- a/test/integration/react-streaming-and-server-components/app/next.config.js +++ b/test/integration/react-streaming-and-server-components/app/next.config.js @@ -1,6 +1,7 @@ const withReact18 = require('../../react-18/test/with-react-18') module.exports = withReact18({ + trailingSlash: true, reactStrictMode: true, onDemandEntries: { maxInactiveAge: 1000 * 60 * 60, diff --git a/test/integration/react-streaming-and-server-components/app/pages/streaming-rsc.server.js b/test/integration/react-streaming-and-server-components/app/pages/streaming-rsc.server.js new file mode 100644 index 000000000000000..ac58f5e9e2c382c --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/streaming-rsc.server.js @@ -0,0 +1,23 @@ +import { Suspense } from 'react' + +let result +let promise +function Data() { + if (result) return result + if (!promise) + promise = new Promise((res) => { + setTimeout(() => { + result = 'next_streaming_data' + res() + }, 500) + }) + throw promise +} + +export default function Page() { + return ( + + + + ) +} diff --git a/test/integration/react-streaming-and-server-components/test/rsc.js b/test/integration/react-streaming-and-server-components/test/rsc.js index 604073c3930fd8e..a01fefe5eb1d605 100644 --- a/test/integration/react-streaming-and-server-components/test/rsc.js +++ b/test/integration/react-streaming-and-server-components/test/rsc.js @@ -82,6 +82,12 @@ export default function (context, { runtime, env }) { expect(await browser.eval('window.beforeNav')).toBe(1) }) + it('should handle streaming server components correctly', async () => { + const browser = await webdriver(context.appPort, '/streaming-rsc') + const content = await browser.eval(`window.document.body.innerText`) + expect(content).toMatchInlineSnapshot('"next_streaming_data"') + }) + // Disable next/image for nodejs runtime temporarily if (runtime === 'edge') { it('should suspense next/image in server components', async () => {