Skip to content

[Beta]: renderToPipeableStream, but just for a part of the page #5663

Closed
@azangru

Description

@azangru

Background

The new React docs say, in the description of the renderToPipeableStream api:

Your root component should return the entire document including the root tag.

I.e. the intention seems to be that React control the full DOM tree, starting from the html element. However, doing so opens the React app to various hydration problems caused by third-party scripts and browser extensions (see e.g. this report from a Sentry developer, or this report by me).

As a response to this problem, some developers seem to be combining the new renderToPipeableStream api with a more traditional approach of rendering the React app into a dedicated DOM element inside the html body. Here is an example I found in the Remix community. The relevant part in that example appears as follows:

const callbackName = isbot(request.headers.get("user-agent"))
    ? "onAllReady"
    : "onShellReady";

/* ... */

const { pipe, abort } = renderToPipeableStream(
  <RemixServer context={remixContext} url={request.url} />,
  {
    [callbackName]: () => {
      const body = new PassThrough();

      responseHeaders.set("Content-Type", "text/html");

      resolve(
        new Response(body, {
          headers: responseHeaders,
          status: didError ? 500 : responseStatusCode,
        })
      );
      const html = `<!DOCTYPE html><html><head><!--start head-->${head}<!--end head--></head><body><div id="root">`;
      body.write(html);
      pipe(body);
      body.write(`</div></body></html>`);
    },
    onShellError(err: unknown) {
      reject(err);
    },
    onError(error: unknown) {
      didError = true;

      console.error(error);
    },
  }
);

Questions

  • Is there anything about the renderToPipeableStream api that requires the rendered React node to be the html element with all its children? If not, do you think it would be worth updating the docs to reflect that?
  • In the code example above, I am worried about this part:
body.write(html);
pipe(body);
body.write(`</div></body></html>`);

Looking at the source code of renderToPipeableStream, I cannot tell whether there is anything that guarantees in the snippet above that the second body.write will happen strictly after the pipe(body) has finished; but then, I am not very well versed in node streams.

Could you please let me know if you have any concerns about the snippet above? If there indeed is a timing problem with the streams, then shouldn't the pipe on the renderToPipeableStream have some way of notifying the consumer that it has finished piping? If, on the other hand, everything is all right with the snippet above, then perhaps this approach merits discussion in the docs?

Metadata

Metadata

Assignees

No one assigned

    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