Skip to content

Server Component response must be flushed inline with SSR #30994

@devknoll

Description

@devknoll

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 ReadableStreams 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).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions