Description
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 thehtml
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?