-
Notifications
You must be signed in to change notification settings - Fork 29.3k
Description
In order to properly hydrate the SSR'd Server Component tree, we have to flush the Server Component data along with it, at least for now. To do that, we should introduce a ServerRoot.client
component:
export function ServerRoot({ req, bootstrap }) {
const id = useId()
const res = useServerResponse(id, req, bootstrap)
return res.readRoot()
}
Here, req
would be some serialized representation of a Server Component request (such as a URL). useServerResponse
would have a different implementation based on whether it was being used for SSR or by the browser. For SSR, that might look like:
export function createSSRHook() {
const rscCache = new Map()
return (id, req, bootstrap) => {
let entry = rscCache.get(id)
if (!entry) {
const [renderStream, forwardStream] = renderFlight(req).tee()
entry = createFromReadableStream(renderStream)
rscCache.set(id, entry)
if (bootstrap) {
res.write(renderToString(
<script dangerouslySetInnerHTML={{ __html: `(self.__next_s=self.__next_s||[]).push(${JSON.stringify([0, id])})` }} />
))
}
forEachStream(forwardStream, chunk => {
res.write(renderToString(
<script dangerouslySetInnerHTML={{ __html: `(self.__next_s=self.__next_s||[]).push(${JSON.stringify([1, id, chunk])})` }} />
))
})
}
return entry
}
}
For SSR, we would render a tree like:
<ServerRoot req={rscReq} bootstrap={true} />
Similarly, the next/client
would install a handler for window.__next_s
calls when it loads, and use it to manage a global map of IDs to ReadableStream
s and enqueue them to be consumed by the browser implementation of useServerResponse
. That implementation would consume the streams from this global map, or kick off a new request if the value is not present.
The bootstrap
part replaces __NEXT_DATA__
, which, by itself, isn't suitable for streaming rendering. Instead, it just tells the client which stream corresponds to the root Server Component, and any initialization needed (such as the webpack asset prefix).