Remix Error Boundaries — Graceful Error Handling
In this tutorial, you will learn about Remix Error Boundaries. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn Remix error boundaries: handle loader and action errors, create fallback UI, catch thrown responses, and prevent full page crashes.
In this lesson, you'll implement error boundaries that catch errors in loaders, actions, and components, rendering fallback UI instead of crashing the entire page.
What You'll Learn
How to define error boundaries, catch thrown responses, handle unexpected errors, nest error boundaries, and provide useful error feedback.
Why It Matters
Errors are inevitable in production. Error boundaries prevent a crash in one route from taking down the entire page, keeping the UI usable.
Real-World Use
DodaZIP's admin panel uses error boundaries at every route level: a crash in the sidebar doesn't affect the main content, and vice versa.
flowchart TD
A[Route Error] --> B{Error Boundary Exists?}
B -->|Yes| C[Render Fallback UI]
B -->|No| D[Bubble to Parent]
D --> E[Root Error Boundary]
style A fill:#121212,color:#fff
Basic Error Boundary
// app/routes/users.$id.tsx
import { useRouteError, isRouteErrorResponse } from "@remix-run/react";
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status} - {error.statusText}</h1>
<p>{error.data}</p>
</div>
);
}
return (
<div>
<h1>Unexpected Error</h1>
<p>{error.message}</p>
</div>
);
}
Catching Thrown Responses
Loaders and actions can throw Response objects:
export const loader = async ({ params }) => {
const user = await db.user.findUnique(params.id);
if (!user) {
throw new Response("User not found", {
status: 404,
statusText: "Not Found",
});
}
if (!user.active) {
throw new Response("Account deactivated", {
status: 403,
});
}
return json(user);
};
The error boundary catches these and renders appropriate messages.
Nested Error Boundaries
Each route can have its own error boundary:
// app/routes/dashboard.tsx (parent)
export function ErrorBoundary() {
return <h1>Dashboard error</h1>;
}
// app/routes/dashboard.analytics.tsx (child)
export function ErrorBoundary() {
return <h1>Analytics error</h1>;
}
An error in the analytics route shows "Analytics error" while the dashboard layout stays intact.
Root Error Boundary
Set a global fallback in app/root.tsx:
export function ErrorBoundary() {
const error = useRouteError();
return (
<html>
<head>
<title>Something went wrong</title>
</head>
<body>
<h1>Application Error</h1>
<p>We're sorry. Please try again later.</p>
<a href="/">Go to Home</a>
</body>
</html>
);
}
Logging Errors
Log errors to an external service:
export function ErrorBoundary() {
const error = useRouteError();
useEffect(() => {
logErrorToService({
message: error.message,
stack: error.stack,
url: window.location.href,
});
}, [error]);
// ...render fallback UI
}
Common Mistakes
- Not checking
isRouteErrorResponse: Use this guard to differentiate between thrown Response objects and unexpected errors. - Using error boundaries for form validation errors: Validation errors should be returned from actions, not thrown. Error boundaries are for unexpected failures.
- Not nesting error boundaries: Without nesting, an error in any child crashes the entire parent layout.
- Forgetting the root error boundary: Unhandled errors bubble to the root. A root error boundary prevents a blank white screen.
- Exposing sensitive error details: In production, don't show stack traces or database error messages to users.
Practice Questions
What hook gets the current error in an error boundary? Answer:
useRouteError()from@remix-run/react. It returns the thrown error or Response.How do you differentiate between a thrown Response and an unexpected error? Answer: Use
isRouteErrorResponse(error). Thrown Responses havestatus,statusText, anddataproperties.What happens if a child route has no error boundary? Answer: The error bubbles up to the nearest parent error boundary. If none exists, the root error boundary catches it.
Should form validation errors be thrown or returned? Answer: Returned as JSON from the action. Thrown errors are for unexpected failures (404, 403, 500).
Challenge
Build a nested route structure with three levels of error boundaries. The root catches everything, the dashboard catches navigation errors, and individual route boundaries handle data-specific errors.
Mini Project
Create a user management system with error boundaries at each level: user list (catches fetch errors), user detail (catches 404), and user edit form (catches save failures).
FAQ
What's Next
Learn about Remix Catch Boundaries for handling expected error states like 404 and 403 responses.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro