// Copyright (c) 2026 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE-APACHE file or at: // https://opensource.org/licenses/Apache-2.0 // Distributed here as part of a work licensed under the AGPL-3.0-only (see LICENSE). import type { AppLoadContext, EntryContext } from "react-router"; import { ServerRouter } from "react-router"; import { isbot } from "isbot"; import { renderToReadableStream } from "react-dom/server"; import { I18nextProvider } from "react-i18next"; import { getInstance } from "~/middleware/i18next"; export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, routerContext: EntryContext, loadContext: AppLoadContext ) { let shellRendered = false; const userAgent = request.headers.get("user-agent"); // [i18n-foundation] The per-request i18next instance created by // `i18nextMiddleware` (app/root.tsx) lives on the Router context, which is // the same object React Router passes here as the load context. Passing it // through `` guarantees the SSR markup is rendered in the // detected language, so the client hydrates without a mismatch. // @ts-expect-error — with `future.v8_middleware` the context is a // `RouterContextProvider`, but the template types this arg as AppLoadContext. const i18n = getInstance(loadContext); const body = await renderToReadableStream( , { 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) { console.error(error); } }, } ); shellRendered = true; // 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 if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) { await body.allReady; } responseHeaders.set("Content-Type", "text/html"); return new Response(body, { headers: responseHeaders, status: responseStatusCode, }); }