import * as Sentry from "@sentry/react-router"; import { PassThrough } from "node:stream"; import { logger } from "~/lib/logger"; import type { AppLoadContext, EntryContext } from "react-router"; import { createReadableStreamFromReadable } from "@react-router/node"; import { ServerRouter } from "react-router"; import { isbot } from "isbot"; import type { RenderToPipeableStreamOptions } from "react-dom/server"; import { renderToPipeableStream } from "react-dom/server"; export const handleError = Sentry.createSentryHandleError({ logErrors: true, }); export const streamTimeout = 5_000; async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, routerContext: EntryContext, // If you have middleware enabled: // loadContext: RouterContextProvider _loadContext: AppLoadContext ) { return new Promise((resolve, reject) => { let shellRendered = false; const userAgent = request.headers.get("user-agent"); // Ensure requests from bots and SPA Mode renders wait for all content to load before responding // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation const readyOption: keyof RenderToPipeableStreamOptions = (userAgent && isbot(userAgent)) || routerContext.isSpaMode ? "onAllReady" : "onShellReady"; // Abort the rendering stream after the `streamTimeout` so it has time to // flush down the rejected boundaries let timeoutId: ReturnType | undefined = setTimeout( () => abort(), streamTimeout + 1000, ); const { pipe, abort } = renderToPipeableStream( , { [readyOption]() { shellRendered = true; const body = new PassThrough({ final(callback) { // Clear the timeout to prevent retaining the closure and memory leak clearTimeout(timeoutId); timeoutId = undefined; callback(); }, }); const stream = createReadableStreamFromReadable(body); responseHeaders.set("Content-Type", "text/html"); pipe(Sentry.getMetaTagTransformer(body)); resolve( new Response(stream, { headers: responseHeaders, status: responseStatusCode, }), ); }, onShellError(error: unknown) { reject(error); }, onError(error: unknown) { responseStatusCode = 500; // Log streaming rendering errors from inside the shell. Don't log // errors encountered during initial shell rendering since they'll // reject and get logged in handleDocumentRequest. if (shellRendered) { logger.error(error); } }, }, ); }); } export default Sentry.wrapSentryHandleRequest(handleRequest);