|
| 1 | +--- |
| 2 | +title: entry.server.tsx |
| 3 | +order: 5 |
| 4 | +--- |
| 5 | + |
| 6 | +# entry.server.tsx |
| 7 | + |
| 8 | +[MODES: framework] |
| 9 | + |
| 10 | +## Summary |
| 11 | + |
| 12 | +<docs-info> |
| 13 | +This file is optional |
| 14 | +</docs-info> |
| 15 | + |
| 16 | +This file is the server-side entry point that controls how your React Router application generates HTTP responses on the server. |
| 17 | + |
| 18 | +This module should render the markup for the current page using a [`<ServerRouter>`][serverrouter] element with the `context` and `url` for the current request. This markup will (optionally) be re-hydrated once JavaScript loads in the browser using the [client entry module][client-entry]. |
| 19 | + |
| 20 | +## Generating `entry.server.tsx` |
| 21 | + |
| 22 | +By default, React Router will handle generating the HTTP Response for you. You can reveal the default entry server file with the following: |
| 23 | + |
| 24 | +```shellscript nonumber |
| 25 | +npx react-router reveal |
| 26 | +``` |
| 27 | + |
| 28 | +## Exports |
| 29 | + |
| 30 | +### `default` |
| 31 | + |
| 32 | +The `default` export of this module is a function that lets you create the response, including HTTP status, headers, and HTML, giving you full control over the way the markup is generated and sent to the client. |
| 33 | + |
| 34 | +```tsx filename=app/entry.server.tsx |
| 35 | +import { PassThrough } from "node:stream"; |
| 36 | +import type { EntryContext } from "react-router"; |
| 37 | +import { createReadableStreamFromReadable } from "@react-router/node"; |
| 38 | +import { ServerRouter } from "react-router"; |
| 39 | +import { renderToPipeableStream } from "react-dom/server"; |
| 40 | + |
| 41 | +export default function handleRequest( |
| 42 | + request: Request, |
| 43 | + responseStatusCode: number, |
| 44 | + responseHeaders: Headers, |
| 45 | + routerContext: EntryContext |
| 46 | +) { |
| 47 | + return new Promise((resolve, reject) => { |
| 48 | + const { pipe, abort } = renderToPipeableStream( |
| 49 | + <ServerRouter |
| 50 | + context={routerContext} |
| 51 | + url={request.url} |
| 52 | + />, |
| 53 | + { |
| 54 | + onShellReady() { |
| 55 | + responseHeaders.set("Content-Type", "text/html"); |
| 56 | + |
| 57 | + const body = new PassThrough(); |
| 58 | + const stream = |
| 59 | + createReadableStreamFromReadable(body); |
| 60 | + |
| 61 | + resolve( |
| 62 | + new Response(stream, { |
| 63 | + headers: responseHeaders, |
| 64 | + status: responseStatusCode, |
| 65 | + }) |
| 66 | + ); |
| 67 | + |
| 68 | + pipe(body); |
| 69 | + }, |
| 70 | + onShellError(error: unknown) { |
| 71 | + reject(error); |
| 72 | + }, |
| 73 | + } |
| 74 | + ); |
| 75 | + }); |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +### `streamTimeout` |
| 80 | + |
| 81 | +If you are [streaming] responses, you can export an optional `streamTimeout` value (in milliseconds) that will control the amount of time the server will wait for streamed promises to settle before rejecting outstanding promises and closing the stream. |
| 82 | + |
| 83 | +It's recommended to decouple this value from the timeout in which you abort the React renderer. You should always set the React rendering timeout to a higher value so it has time to stream down the underlying rejections from your `streamTimeout`. |
| 84 | + |
| 85 | +```tsx lines=[1-2,13-15] |
| 86 | +// Reject all pending promises from handler functions after 10 seconds |
| 87 | +export const streamTimeout = 10000; |
| 88 | + |
| 89 | +export default function handleRequest(...) { |
| 90 | + return new Promise((resolve, reject) => { |
| 91 | + // ... |
| 92 | + |
| 93 | + const { pipe, abort } = renderToPipeableStream( |
| 94 | + <ServerRouter context={routerContext} url={request.url} />, |
| 95 | + { /* ... */ } |
| 96 | + ); |
| 97 | + |
| 98 | + // Abort the streaming render pass after 11 seconds to allow the rejected |
| 99 | + // boundaries to be flushed |
| 100 | + setTimeout(abort, streamTimeout + 1000); |
| 101 | + }); |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +### `handleDataRequest` |
| 106 | + |
| 107 | +You can export an optional `handleDataRequest` function that will allow you to modify the response of a data request. These are the requests that do not render HTML, but rather return the `loader` and `action` data to the browser once client-side hydration has occurred. |
| 108 | + |
| 109 | +```tsx |
| 110 | +export function handleDataRequest( |
| 111 | + response: Response, |
| 112 | + { |
| 113 | + request, |
| 114 | + params, |
| 115 | + context, |
| 116 | + }: LoaderFunctionArgs | ActionFunctionArgs |
| 117 | +) { |
| 118 | + response.headers.set("X-Custom-Header", "value"); |
| 119 | + return response; |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +### `handleError` |
| 124 | + |
| 125 | +By default, React Router will log encountered server-side errors to the console. If you'd like more control over the logging, or would like to also report these errors to an external service, then you can export an optional `handleError` function which will give you control (and will disable the built-in error logging). |
| 126 | + |
| 127 | +```tsx |
| 128 | +export function handleError( |
| 129 | + error: unknown, |
| 130 | + { |
| 131 | + request, |
| 132 | + params, |
| 133 | + context, |
| 134 | + }: LoaderFunctionArgs | ActionFunctionArgs |
| 135 | +) { |
| 136 | + if (!request.signal.aborted) { |
| 137 | + sendErrorToErrorReportingService(error); |
| 138 | + console.error(formatErrorForJsonLogging(error)); |
| 139 | + } |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +_Note that you generally want to avoid logging when the request was aborted, since React Router's cancellation and race-condition handling can cause a lot of requests to be aborted._ |
| 144 | + |
| 145 | +**Streaming Rendering Errors** |
| 146 | + |
| 147 | +When you are streaming your HTML responses via [`renderToPipeableStream`][rendertopipeablestream] or [`renderToReadableStream`][rendertoreadablestream], your own `handleError` implementation will only handle errors encountered during the initial shell render. If you encounter a rendering error during subsequent streamed rendering you will need to handle these errors manually since the React Router server has already sent the Response by that point. |
| 148 | + |
| 149 | +For `renderToPipeableStream`, you can handle these errors in the `onError` callback function. You will need to toggle a boolean in `onShellReady` so you know if the error was a shell rendering error (and can be ignored) or an async |
| 150 | + |
| 151 | +For an example, please refer to the default [`entry.server.tsx`][node-streaming-entry-server] for Node. |
| 152 | + |
| 153 | +**Thrown Responses** |
| 154 | + |
| 155 | +Note that this does not handle thrown `Response` instances from your `loader`/`action` functions. The intention of this handler is to find bugs in your code which result in unexpected thrown errors. If you are detecting a scenario and throwing a 401/404/etc. `Response` in your `loader`/`action` then it's an expected flow that is handled by your code. If you also wish to log, or send those to an external service, that should be done at the time you throw the response. |
| 156 | + |
| 157 | +[client-entry]: ./entry.client.tsx |
| 158 | +[serverrouter]: ../components/ServerRouter |
| 159 | +[streaming]: ../how-to/suspense |
| 160 | +[rendertopipeablestream]: https://react.dev/reference/react-dom/server/renderToPipeableStream |
| 161 | +[rendertoreadablestream]: https://react.dev/reference/react-dom/server/renderToReadableStream |
| 162 | +[node-streaming-entry-server]: https://github.com/remix-run/react-router/blob/dev/packages/react-router-dev/config/defaults/entry.server.node.tsx |
0 commit comments