useRouteError

When a loader, an action or a route's own render throws, React Router 139,386 does not crash the app. It renders the nearest matched route's error boundary in place of that route's element, so a failed product page leaves the site chrome intact. A route opts in with ErrorBoundary or the older errorElement. The boundary gets no props, so it reads the thrown value with useRouteError().

Distinguishing an expected 404 from a genuine bugJavaScript
import { isRouteErrorResponse, useRouteError } from 'react-router';
function ProductError() {
  const error = useRouteError();
  if (isRouteErrorResponse(error))         // an expected, HTTP-shaped failure
    return <p>{error.status} {error.statusText}: {error.data}</p>;
  return <p>Something went wrong: {error.message ?? 'Unknown error'}</p>;
}

Hang it on the route as ErrorBoundary: ProductError, and let the loader throw data('No product with that id', { status: 404 }) when the record is missing. That marks an expected failure — a missing record, a forbidden request — and isRouteErrorResponse() recognizes it, giving you status, statusText and data. Anything else is almost certainly a bug: a plain Error worth logging.

Put one boundary on the root route so nothing can reach a blank page, and narrower ones only where a partial failure should stay partial — a comments panel that fails without taking the article with it. A router with none falls back to React Router's built-in page, which prints the stack and is for development only.