Building a Multi-Page SPA with React RouterEntry 13 of 20
Error Handling with errorElement and useRouteError
When a loader, action, or component throws, React Router renders the nearest errorElement. Inside it, useRouteError() returns what was thrown.
Notes
- Errors bubble up to the closest ancestor route with an
errorElement, so one on the root route covers the whole app. - The
errorElementreplaces that route'selement, so include navigation in the error page if you want it visible. - Throw
data(payload, { status })from a loader to send a status code and message.isRouteErrorResponse(error)detects it; readerror.statusanderror.data. - A plain
throw new Error(...)reaches the error page as anErrorinstance. - An unknown URL also renders the root
errorElement(as a 404 route error response) if there's no*route.
What / Why / When
- What
Route-scoped error boundaries.
- Why
A failing request shows a friendly page instead of crashing the app.
- When
Always at the root; add more on routes that need a more specific message.
Code compare
// pages/Events.tsximport { data } from "react-router";export async function loader() { try { return await getEvents(); } catch { throw data({ message: "Could not fetch events." }, { status: 500 }); }}// pages/Error.tsximport { isRouteErrorResponse, useRouteError } from "react-router";import MainNavigation from "../components/MainNavigation";export default function ErrorPage() { const error = useRouteError(); let title = "An error occurred!"; let message = "Something went wrong!"; if (isRouteErrorResponse(error)) { if (error.status === 404) { title = "Not found!"; message = "Could not find resource or page."; } else { message = error.data?.message ?? message; } } else if (error instanceof Error) { message = error.message; } return ( <> <MainNavigation /> <main> <h1>{title}</h1> <p>{message}</p> </main> </> );}// App.tsx{ path: '/', element: <RootLayout />, errorElement: <ErrorPage />, children: [/* ... */] }Angular has no route-level error boundary. Handle errors in the resolver and redirect to an error page, or register a global handler with withNavigationErrorHandler().
// events.resolver.tsexport const eventsResolver: ResolveFn<EventItem[]> = () => { const router = inject(Router); return inject(EventsService) .getEvents() .pipe( catchError(() => of( new RedirectCommand(router.parseUrl("/error?message=Could not fetch events."), { skipLocationChange: true, }), ), ), );};// error.component.ts@Component({ selector: "app-error", template: ` <h1>An error occurred!</h1> <p>{{ message() ?? "Something went wrong!" }}</p> `,})export class ErrorComponent { message = input<string>(); // bound from ?message=}Differences and gotchas
- React's
errorElementrenders in place at the failing route. Angular redirects to a separate error route (skipLocationChangekeeps the original URL visible). - Unexpected errors elsewhere in Angular go to the global
ErrorHandler.
Quick reference
| React | Angular |
|---|---|
errorElement | Error route + RedirectCommand / withNavigationErrorHandler() |
useRouteError() | Error data via inputs / navigation state |
throw data(msg, { status }) | catchError in resolver |
| Bubbles to nearest ancestor | Global ErrorHandler for the rest |