|
1 | 1 | --- |
2 | 2 | title: Error Boundaries |
3 | | -hidden: true |
4 | 3 | --- |
| 4 | + |
| 5 | +# Error Boundaries |
| 6 | + |
| 7 | +To avoid rendering an empty page to users, route modules will automatically catch errors in your code and render the closest `ErrorBoundary`. |
| 8 | + |
| 9 | +Error boundaries are not intended for error reporting or rendering form validation errors. Please see [Form Validation](./form-validation) and [Error Reporting](./error-reporting) instead. |
| 10 | + |
| 11 | +## 1. Add a root error boundary |
| 12 | + |
| 13 | +All applications should at a minimum export a root error boundary. This one handles the three main cases: |
| 14 | + |
| 15 | +- Thrown `data` with a status code and text |
| 16 | +- Instances of errors with a stack trace |
| 17 | +- Randomly thrown values |
| 18 | + |
| 19 | +```tsx filename=root.tsx |
| 20 | +import { Route } from "./+types/root"; |
| 21 | + |
| 22 | +export function ErrorBoundary({ |
| 23 | + error, |
| 24 | +}: Route.ErrorBoundaryProps) { |
| 25 | + console.error(error); |
| 26 | + if (isRouteErrorResponse(error)) { |
| 27 | + return ( |
| 28 | + <> |
| 29 | + <h1> |
| 30 | + {error.status} {error.statusText} |
| 31 | + </h1> |
| 32 | + <p>{error.data}</p> |
| 33 | + </> |
| 34 | + ); |
| 35 | + } else if (error instanceof Error) { |
| 36 | + return ( |
| 37 | + <div> |
| 38 | + <h1>Error</h1> |
| 39 | + <p>{error.message}</p> |
| 40 | + <p>The stack trace is:</p> |
| 41 | + <pre>{error.stack}</pre> |
| 42 | + </div> |
| 43 | + ); |
| 44 | + } else { |
| 45 | + return <h1>Unknown Error</h1>; |
| 46 | + } |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +## 2. Write a bug |
| 51 | + |
| 52 | +It's not recommended to intentionally throw errors to force the error boundary to render as a means of control flow. Error Boundaries are primarily for catching unintentional errors in your code. |
| 53 | + |
| 54 | +```tsx |
| 55 | +export async function loader() { |
| 56 | + return undefined(); |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +This will render the `instanceof Error` branch of the UI from step 1. |
| 61 | + |
| 62 | +This is not just for loaders, but for all route module APIs: loaders, actions, components, headers, links, and meta. |
| 63 | + |
| 64 | +## 3. Throw data in loaders/actions |
| 65 | + |
| 66 | +There are exceptions to the rule in #2, especially 404s. You can intentionally `throw data()` (with a proper status code) to the closest error boundary when your loader can't find what it needs to render the page. Throw a 404 and move on. |
| 67 | + |
| 68 | +```tsx |
| 69 | +import { data } from "react-router"; |
| 70 | + |
| 71 | +export async function loader({ params }) { |
| 72 | + let record = await fakeDb.getRecord(params.id); |
| 73 | + if (!record) { |
| 74 | + throw data("Record Not Found", { status: 404 }); |
| 75 | + } |
| 76 | + return record; |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +This will render the `isRouteErrorResponse` branch of the UI from step 1. |
| 81 | + |
| 82 | +## 4. Nested error boundaries |
| 83 | + |
| 84 | +When an error is thrown, the "closest error boundary" will be rendered. Consider these nested routes: |
| 85 | + |
| 86 | +```tsx filename="routes.ts" |
| 87 | +// ✅ has error boundary |
| 88 | +route("/app", "app.tsx", [ |
| 89 | + // ❌ no error boundary |
| 90 | + route("invoices", "invoices.tsx", [ |
| 91 | + // ✅ has error boundary |
| 92 | + route("invoices/:id", "invoice-page.tsx", [ |
| 93 | + // ❌ no error boundary |
| 94 | + route("payments", "payments.tsx"), |
| 95 | + ]), |
| 96 | + ]), |
| 97 | +]); |
| 98 | +``` |
| 99 | + |
| 100 | +The following table shows which error boundary will render given the origin of the error: |
| 101 | + |
| 102 | +| error origin | rendered boundary | |
| 103 | +| ---------------- | ----------------- | |
| 104 | +| app.tsx | app.tsx | |
| 105 | +| invoices.tsx | app.tsx | |
| 106 | +| invoice-page.tsx | invoice-page.tsx | |
| 107 | +| payments.tsx | invoice-page.tsx | |
| 108 | + |
| 109 | +## Error Sanitization |
| 110 | + |
| 111 | +In production mode, any errors that happen on the server are automatically sanitized before being sent to the browser to prevent leaking any sensitive server information (like stack traces). |
| 112 | + |
| 113 | +This means that a thrown `Error` will have a generic message and no stack trace in production in the browser. The original error is untouched on the server. |
| 114 | + |
| 115 | +Also note that data sent with `throw data(yourData)` is not sanitized as the data there is intended to be rendered. |
0 commit comments