Generate entry.server
with renderToReadableStream
when in non-node
environment
#12712
kinggoesgaming
started this conversation in
Proposals
Replies: 1 comment
-
Here's an implementation that checks if import { isbot } from "isbot";
import type { RenderToPipeableStreamOptions } from "react-dom/server";
import { ServerRouter, type EntryContext } from "react-router";
export const streamTimeout = 5_000;
export default async function handleRequest(
request: Request,
status: number,
headers: Headers,
context: EntryContext,
) {
let shellRendered = false;
const userAgent = request.headers.get("user-agent");
const shouldWaitForAllContent = isbot(userAgent) || context.isSpaMode;
if ("renderToReadableStream" in await import("react-dom/server")) {
const { renderToReadableStream } = await import("react-dom/server");
const stream = await renderToReadableStream(
<ServerRouter context={context} url={request.url} />,
{
onError(error) {
// biome-ignore lint/style/noParameterAssign: this is required for this server to indicate failure
status = 500;
if (shellRendered) {
console.error(error);
}
}
}
);
shellRendered = true;
if (shouldWaitForAllContent) {
await stream.allReady;
}
headers.set("Content-Type", "text/html");
return new Response(stream, {
headers,
status,
});
}
const { PassThrough } = await import("node:stream");
const { renderToPipeableStream } = await import("react-dom/server");
const { createReadableStreamFromReadable } = await import("@react-router/node");
const readyOption: keyof RenderToPipeableStreamOptions = (userAgent && isbot(userAgent)) || context.isSpaMode ? "onAllReady" : "onShellReady";
return new Promise<Response>((resolve, reject) => {
const {pipe, abort} = renderToPipeableStream(
<ServerRouter context={context} url={request.url} />,
{
[readyOption]: () => {
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
headers.set("Content-Type", "text/html");
resolve(new Response(stream, {
headers,
status,
}));
pipe(body);
},
onShellError(error) {
reject(error);
},
onError(error) {
status = 500;
if (shellRendered) {
console.error(error);
}
},
},
);
setTimeout(abort, streamTimeout + 1000);
});
}; I just hacked it together in about 20 minutes, so it could be improved further but this is a good start. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
When
entry.server
is not present in the app directory,react-router
plugin adds a default implementation that usesrenderToPipeableStream
for server side rendering.When
entry.server
is present, thereact-router
plugin instead uses that implementation.Bun, Browser, Cloudflare Edge, Cloudflare Worker, Deno implementations of
react-dom/server
supportrenderToReadableStream
. It would nice to support the spec compliant streams.This specifically came to my attention when I tried using
renderToReadableStream
in one of my project and it works fine in production bun server. However as thevite
dev server uses anode
server behind the scenes,renderToReadableStream
not being present becomes an issue.Edit: Bun implementation does not have
renderToPipeableStream
. I didn't realize it was falling back tonode
runtime, until I checked.Here's a sample output of
entry.server
usingrenderToReadableStream
:Related: remix-run/react-router-website#152 (comment)
Beta Was this translation helpful? Give feedback.
All reactions