Error boundaries
If a loader, action or component throws, the framework does not show a blank screen. It looks for the nearest route module that exports ErrorBoundary and renders it instead of the page. The layout and header above it stay in place.
The root boundary
The project template already has a boundary in app/root.tsx — it catches everything not caught lower down. If it is missing, add a similar one:
// app/root.tsx
import { isRouteErrorResponse } from "react-router";
import type { Route } from "./+types/root";
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
if (isRouteErrorResponse(error)) {
return <h1>{error.status} {error.statusText}</h1>;
}
return <h1>Something went wrong</h1>;
}Open /abc — an address that does not exist. "404 Not Found" appears: the framework creates this response itself when no route matches.
A boundary for the post
The post loader from the loaders lesson throws a 404 response. Add a boundary to app/routes/post.tsx:
// app/routes/post.tsx
import { Link, isRouteErrorResponse } from "react-router";
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
if (isRouteErrorResponse(error) && error.status === 404) {
return (
<>
<h1>Post not found</h1>
<Link to="/">Back to the posts</Link>
</>
);
}
throw error;
}At /posts/99 the blog layout shows "Post not found" with a link. Other errors are rethrown, and the root boundary shows them.
Two kinds of errors
| Kind | How it happens | How to recognise it |
|---|---|---|
| Expected | throw new Response(...) or throw data(...) | isRouteErrorResponse(error) is true, it has status and data |
| Unexpected | A typo in code, the database is down | error instanceof Error, it has message |
You throw expected errors yourself to stop loading: "not found", "forbidden". The text from new Response("Post not found", ...) is available as error.data.
Throwing with data
Instead of a Response you can throw data with any object:
// app/routes/post.tsx
import { data } from "react-router";
throw data({ postId: params.postId }, { status: 404 });In the boundary error.data.postId tells which post was requested.
Common mistake
Showing error.message and error.stack to visitors in the boundary. That is handy during development, but in production an error text can reveal how the app is built. The template's root boundary shows the stack only when import.meta.env.DEV is true; follow the same rule in your boundaries.