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`
This commit is contained in:
Jiachi Liu 2022-02-21 13:40:59 +01:00 committed by GitHub
parent 0243e6e95d
commit f110a3735b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 1 deletions

View file

@ -702,7 +702,9 @@ if (process.env.__NEXT_RSC) {
writer.write(encoder.encode(val)) writer.write(encoder.encode(val))
}) })
buffer.length = 0 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) serverDataWriter.set(key, writer)
} }

View file

@ -1,6 +1,7 @@
const withReact18 = require('../../react-18/test/with-react-18') const withReact18 = require('../../react-18/test/with-react-18')
module.exports = withReact18({ module.exports = withReact18({
trailingSlash: true,
reactStrictMode: true, reactStrictMode: true,
onDemandEntries: { onDemandEntries: {
maxInactiveAge: 1000 * 60 * 60, maxInactiveAge: 1000 * 60 * 60,

View file

@ -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 (
<Suspense fallback="next_streaming_fallback">
<Data />
</Suspense>
)
}

View file

@ -82,6 +82,12 @@ export default function (context, { runtime, env }) {
expect(await browser.eval('window.beforeNav')).toBe(1) 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 // Disable next/image for nodejs runtime temporarily
if (runtime === 'edge') { if (runtime === 'edge') {
it('should suspense next/image in server components', async () => { it('should suspense next/image in server components', async () => {