diff --git a/docs/file-conventions/entry.server.md b/docs/file-conventions/entry.server.md index 28e8c78cabc..270a0105e1d 100644 --- a/docs/file-conventions/entry.server.md +++ b/docs/file-conventions/entry.server.md @@ -37,6 +37,21 @@ export function onUnhandledError( } ``` +### Streaming Rendering Errors + +When you are streaming your HTML responses via [`renderToPipeableStream`][rendertopipeablestream] or [`renderToReadableStream`][rendertoreadablestream], your own `onUnhandledError` implementation will only handle errors encountered uring the initial shell render. If you encounter a rendering error during subsequent streamed rendering you will need handle these errors manually since the Remix server has already sent the Response by that point. + +- For `renderToPipeableStream`, you can handle these errors in the `onError` callback function. You will need to toggle a boolean when the in `onShellReady` so you know if the error was a shell rendering error (and can be ignored) or an async rendering error (and must be handled). + - For an exmaple, please see the default [`entry.server.tsx`][node-streaming-entry-server] for Node. +- For `renderToReadableStream`, you can handle these errors in the `onError` callback function + - For an example, please see the default [`entry.server.tsx`][cloudflare-streaming-entry-server] for Cloudflare + +### Thrown Responses + 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. [browser-entry-module]: ./entry.client +[rendertopipeablestream]: https://react.dev/reference/react-dom/server/renderToPipeableStream +[rendertoreadablestream]: https://react.dev/reference/react-dom/server/renderToReadableStream +[node-streaming-entry-server]: https://github.com/remix-run/remix/blob/main/packages/remix-dev/config/defaults/node/entry.server.react-stream.tsx +[cloudflare-streaming-entry-server]: https://github.com/remix-run/remix/blob/main/packages/remix-dev/config/defaults/cloudflare/entry.server.react-stream.tsx diff --git a/packages/remix-dev/config/defaults/cloudflare/entry.server.react-stream.tsx b/packages/remix-dev/config/defaults/cloudflare/entry.server.react-stream.tsx index 95b3f71dd52..a393a5228ea 100644 --- a/packages/remix-dev/config/defaults/cloudflare/entry.server.react-stream.tsx +++ b/packages/remix-dev/config/defaults/cloudflare/entry.server.react-stream.tsx @@ -14,6 +14,8 @@ export default async function handleRequest( { signal: request.signal, onError(error: unknown) { + // Log streaming rendering errors from inside the shell + console.error(error); responseStatusCode = 500; }, } diff --git a/packages/remix-dev/config/defaults/deno/entry.server.react-stream.tsx b/packages/remix-dev/config/defaults/deno/entry.server.react-stream.tsx index 6fa31ced316..91a1d11e52b 100644 --- a/packages/remix-dev/config/defaults/deno/entry.server.react-stream.tsx +++ b/packages/remix-dev/config/defaults/deno/entry.server.react-stream.tsx @@ -14,6 +14,8 @@ export default async function handleRequest( { signal: request.signal, onError(error: unknown) { + // Log streaming rendering errors from inside the shell + console.error(error); responseStatusCode = 500; }, } diff --git a/packages/remix-dev/config/defaults/node/entry.server.react-stream.tsx b/packages/remix-dev/config/defaults/node/entry.server.react-stream.tsx index e7952b49d5a..e285b41c7ad 100644 --- a/packages/remix-dev/config/defaults/node/entry.server.react-stream.tsx +++ b/packages/remix-dev/config/defaults/node/entry.server.react-stream.tsx @@ -77,6 +77,7 @@ function handleBrowserRequest( remixContext: EntryContext ) { return new Promise((resolve, reject) => { + let shellRendered = false; const { pipe, abort } = renderToPipeableStream( , { onShellReady() { + shellRendered = true; const body = new PassThrough(); responseHeaders.set("Content-Type", "text/html"); @@ -102,6 +104,12 @@ function handleBrowserRequest( reject(error); }, onError(error: unknown) { + // 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); + } responseStatusCode = 500; }, }