How to Build a 404 Page with React Router

Build a 404 page with React Router by adding a catch-all route that renders a not found component when no other path matches.

5 min read

A 404 page in React Router appears when the URL matches no route. You build it with a catch-all route whose path is *, placed last in the Routes list. This article covers the declarative mode and the data mode error boundary.

A dedicated page beats a blank screen. It tells visitors the link is dead and points them somewhere useful.

Add a catch-all route

A catch-all route uses * as its path, which matches any URL that the earlier routes did not. Place it last.

App.jsxApp.jsx
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="*" element={<NotFound />} />
</Routes>

When no earlier route matches, React Router renders NotFound. The order matters, because the catch-all accepts everything. If the Routes setup is new, the React Router tutorial covers it.

React Router ranks routes by specificity, but the catch-all is the fallback for anything with no match at all. Keep it last so a future route does not get swallowed.

The catch-all also works in nested trees, where it renders inside the parent layout when nested under that parent.

Build the NotFound component

The not found component is a normal page. It should tell the user what happened and offer a way back.

App.jsxApp.jsx
import { Link } from "react-router";
 
export default function NotFound() {
  return (
    <main>
      <h1>Page not found</h1>
      <p>The page you requested does not exist.</p>
      <Link to="/">Back to home</Link>
    </main>
  );
}

The Link sends the user to the home route. A heading and a short message keep the page useful instead of showing a blank screen.

You can echo the bad URL to help users report broken links. Read location.pathname with useLocation inside the component and render it in the message.

The path is only for display, so there is no need to store it in state.

Show the requested path

Echo the bad URL so users can report it. Read the path from useLocation inside the not found component.

App.jsxApp.jsx
import { useLocation } from "react-router";
 
export default function NotFound() {
  const location = useLocation();
  return <p>No page at {location.pathname}</p>;
}

This small addition makes a 404 actionable instead of generic.

Show a 404 in data mode

In data mode, route modules can export an ErrorBoundary that reads the error with useRouteError. Check for a 404 response and render the same not found page.

App.jsxApp.jsx
import { useRouteError, isRouteErrorResponse } from "react-router";
 
export function ErrorBoundary() {
  const error = useRouteError();
  if (isRouteErrorResponse(error) && error.status === 404) {
    return <NotFound />;
  }
  return <p>Something went wrong.</p>;
}

isRouteErrorResponse narrows the error to a 4xx or 5xx response, so you can branch on the status. Data mode and its error boundaries are covered in React Router loaders and actions explained.

A loader can also throw a 404 response directly, which lands in the same ErrorBoundary with a status you can check.

Common mistakes

  • Placing the catch-all before other routes, so it shadows them.
  • Forgetting a way back home, leaving users stuck on the error page.
  • Using path="/" as a catch-all, which only matches the root.
Rune AI

Rune AI

Key Insights

  • Add a catch-all route with path * as the last route.
  • Render a NotFound component with a link back home.
  • Keep the catch-all last so it does not shadow real routes.
  • Use useRouteError and isRouteErrorResponse in data mode.
RunePowered by Rune AI

Frequently Asked Questions

Where does the catch-all route go?

Last in the Routes list. The path * matches any URL the earlier routes did not, so it must come after all the real routes.

Does the 404 page need a server?

No. In declarative mode the catch-all route runs in the browser and shows the not found component for any unmatched URL.

Can I show the requested path?

Yes. Read location.pathname with useLocation inside the NotFound component and render it in the message.

Conclusion

Build a 404 page with a catch-all route whose path is *, placed last in Routes. Render a not found component with a link back home. In data mode, use a route ErrorBoundary with useRouteError and isRouteErrorResponse.