Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

KindHow it happensHow to recognise it
Expectedthrow new Response(...) or throw data(...)isRouteErrorResponse(error) is true, it has status and data
UnexpectedA typo in code, the database is downerror 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.

Thanks for reading!

Donate to the site — we work hard for you.