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
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
When
entry.serveris not present in the app directory,react-routerplugin adds a default implementation that usesrenderToPipeableStreamfor server side rendering.When
entry.serveris present, thereact-routerplugin instead uses that implementation.Bun, Browser, Cloudflare Edge, Cloudflare Worker, Deno implementations of
react-dom/serversupportrenderToReadableStream. It would nice to support the spec compliant streams.This specifically came to my attention when I tried using
renderToReadableStreamin one of my project and it works fine in production bun server. However as thevitedev server uses anodeserver behind the scenes,renderToReadableStreamnot being present becomes an issue.Edit: Bun implementation does not have
renderToPipeableStream. I didn't realize it was falling back tonoderuntime, until I checked.Here's a sample output of
entry.serverusingrenderToReadableStream:Related: remix-run/react-router-website#152 (comment)
Beta Was this translation helpful? Give feedback.
All reactions