One of the easiest ways to improve your React application's user experience is by handling unexpected errors gracefully.
Instead of showing users a blank white screen or cryptic error messages, React Router provides the useRouteError hook along with route-level Error Boundaries to display meaningful fallback UI whenever something goes wrong.
In this guide, you'll learn how to implement professional error handling in React Router step by step.
Why Do You Need Error Handling?
Applications can fail for many reasons:
- API requests fail
- Invalid route parameters
- Missing resources
- JavaScript runtime errors
- Loader or action exceptions
Without proper handling, users often see:
- β Blank pages
- β Console errors
- β Broken UI
A custom error page gives users a much better experience while helping developers debug problems.
What is useRouteError?
useRouteError is a hook provided by React Router that allows an Error Boundary component to access the error that caused the route to fail.
Whenever:
- a loader throws an error,
- an action throws an error,
- or a rendering error occurs inside a route,
React Router automatically renders the route's errorElement instead of the normal page.
Project Structure
src/
βββ App.tsx
βββ pages/
β βββ Home.tsx
β βββ Users.tsx
β βββ ErrorPage.tsx
βββ router.tsx
Step 1 β Create an Error Page
Create a reusable error component.
import { useRouteError, isRouteErrorResponse } from "react-router-dom";
export default function ErrorPage() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status}</h1>
<p>{error.statusText}</p>
</div>
);
}
return (
<div>
<h1>Oops!</h1>
<p>Something went wrong.</p>
</div>
);
}
What's happening here?
-
useRouteError()returns the error thrown by the current route. -
isRouteErrorResponse()checks whether the error is a React RouterResponse. - If it's a router error, we display the HTTP status.
- Otherwise, we show a generic fallback message.
Step 2 β Register the Error Boundary
Attach your error page using errorElement.
import { createBrowserRouter } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
errorElement: <ErrorPage />,
},
]);
Now every error inside this route will automatically render your custom error page.
Step 3 β Throw an Error
Inside a loader:
export async function loader() {
throw new Response("Not Found", {
status: 404,
});
}
Or throw a normal JavaScript error:
throw new Error("Something broke!");
React Router will immediately render the ErrorPage.
Step 4 β Handle Different HTTP Status Codes
Instead of showing one generic message, provide helpful feedback.
if (isRouteErrorResponse(error)) {
switch (error.status) {
case 404:
return <h1>Page Not Found</h1>;
case 401:
return <h1>Unauthorized</h1>;
case 500:
return <h1>Internal Server Error</h1>;
default:
return <h1>Something went wrong.</h1>;
}
}
Users instantly understand what happened.
Step 5 β Handle Unexpected JavaScript Errors
Not every error comes from a loader.
if (error instanceof Error) {
return (
<>
<h1>Unexpected Error</h1>
<p>{error.message}</p>
</>
);
}
This is useful during development and for catching runtime exceptions.
Route-Level Error Boundaries
One of React Router's best features is that every route can have its own Error Boundary.
{
path: "/users",
element: <Users />,
loader: usersLoader,
errorElement: <UsersError />
}
If the /users page fails, only that section is replaced with the error UI while the rest of your application continues working normally.
This keeps your app resilient and improves the overall user experience.
Best Practices
- β
Always define an
errorElement. - β
Use
isRouteErrorResponse()before accessingstatus. - β Display friendly messages instead of technical errors.
- β
Handle common HTTP status codes like
404,401, and500. - β Log unexpected errors to services such as Sentry.
- β Create reusable error components for consistency.
- β Use route-level boundaries whenever possible.
Common Mistakes
β Forgetting errorElement
{
path: "/",
element: <Home />
}
Without an errorElement, your users won't see your custom error page.
β Assuming Every Error is a Response
Always check first:
isRouteErrorResponse(error)
Otherwise, accessing status may throw another error.
β Showing Sensitive Information
Avoid exposing stack traces or server details in production.
Instead:
- Show a friendly message.
- Log the real error internally.
- Help users recover with navigation or retry buttons.
Final Thoughts
A polished application isn't just about beautiful UIβit also handles failures gracefully.
By combining useRouteError with route-level errorElement components, you can catch loader failures, runtime exceptions, and HTTP errors without crashing the entire application.
It's a small feature that dramatically improves both the developer experience and the end-user experience.
πΊ Prefer Video Tutorials?
If you'd rather follow along visually, check out my complete YouTube tutorial where I explain everything step by step with practical examples.
π₯ Watch here: https://www.youtube.com/watch?v=yPsco6gnFuc
π Learn React by Building a Production-Ready Ecommerce App
If you're serious about mastering modern React development, don't miss my Modern React Ecommerce Series, where you'll build a complete production-ready application using technologies like:
- React
- TypeScript
- React Router
- TanStack Query
- Authentication & Authorization
- Stripe Payments
- Firebase
- Responsive UI
- Modern Folder Structure
- Best Practices
Here is a Youtube video:
https://www.youtube.com/watch?v=SdITjnl-zo
By the end of the series, you'll have the confidence to build scalable React applications from scratch.
Happy Coding! π
Top comments (0)