g1t/apps/web/app/entry.server.tsx

73 lines2,184 bytesCodeBlame
1import type { EntryContext, RouterContextProvider, ServerInstrumentation } from "react-router";
2import { ServerRouter } from "react-router";
3import { isbot } from "isbot";
4import { renderToReadableStream } from "react-dom/server";
5
6import { recordHandler } from "./lib/perf.server";
7
8// React Router's own server entry, plus the timing of every loader and
9// action for the Server-Timing header (lib/perf.server.ts).
10
11export const streamTimeout = 5_000;
12
13export const instrumentations: ServerInstrumentation[] = [
14 {
15 route(route) {
16 route.instrument({
17 async loader(handle) {
18 const started = Date.now();
19 await handle();
20 recordHandler(route.id, "loader", Date.now() - started);
21 },
22 async action(handle) {
23 const started = Date.now();
24 await handle();
25 recordHandler(route.id, "action", Date.now() - started);
26 },
27 });
28 },
29 },
30];
31
32export default async function handleRequest(
33 request: Request,
34 responseStatusCode: number,
35 responseHeaders: Headers,
36 routerContext: EntryContext,
37 _loadContext: RouterContextProvider,
38) {
39 // https://httpwg.org/specs/rfc9110.html#HEAD
40 if (request.method.toUpperCase() === "HEAD") {
41 return new Response(null, {
42 status: responseStatusCode,
43 headers: responseHeaders,
44 });
45 }
46
47 let shellRendered = false;
48 const userAgent = request.headers.get("user-agent");
49
50 const body = await renderToReadableStream(<ServerRouter context={routerContext} url={request.url} />, {
51 signal: AbortSignal.timeout(streamTimeout + 1000),
52 onError(error: unknown) {
53 responseStatusCode = 500;
54 // Errors while streaming after the shell; those in the shell reject
55 // and are logged by React Router.
56 if (shellRendered) {
57 console.error(error);
58 }
59 },
60 });
61 shellRendered = true;
62
63 // Crawlers get the whole page at once, deferred panels included.
64 if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
65 await body.allReady;
66 }
67
68 responseHeaders.set("Content-Type", "text/html");
69 return new Response(body, {
70 headers: responseHeaders,
71 status: responseStatusCode,
72 });
73}