Full-project lesson · needs Node.js, npm and real project files, so it does not run in the in-page playground.
Users mistype addresses, follow old links and open pages that no longer exist. A good app shows a clear not found page with a way back, instead of a blank screen.
The catch-all route
A route with path="*" matches any URL that no other route matched:
import { Routes, Route, Link, useParams } from 'react-router'
const PRODUCTS = [{ id: 1, name: 'Keyboard' }, { id: 2, name: 'Mouse' }]
function Home() {
return <h1>Home page</h1>
}
function ProductDetail() {
const { id } = useParams()
const product = PRODUCTS.find((p) => String(p.id) === id)
// A valid route, but no such product: show the same not-found page
if (!product) return <NotFound />
return <h1>Product {product.id}: {product.name}</h1>
}
function NotFound() {
return (
<div role="alert">
<h1>Page not found</h1>
<p>The page you are looking for does not exist or has moved.</p>
<Link to="/">Go to the home page</Link>
</div>
)
}
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>
)
}
export default App
Tested in a real browser: an unknown URL shows the 404 page, the link takes the user home, and /products/abc (a route that matches, but with no such product) shows the same page.
Two kinds of “not found”
- No route matches the URL (a typo, an old link). The
*route handles it. - The route matches but the data does not exist (
/products/9999). Your component must check and show the not-found UI, asProductDetaildoes. Do not leave users on an empty page, and do not crash onundefined.
What makes a good 404 page
- Say plainly that the page was not found.
- Keep the site navigation so users can recover; render the 404 inside your normal layout (put the
*route inside the layout route). - Offer useful ways out: a link home, search, popular pages.
- Do not blame the user; avoid jokes that hide the message.
Errors during loading or rendering
In data mode (createBrowserRouter), routes can name an error screen. React Router renders it if a loader or a component in that route throws:
import { useRouteError, isRouteErrorResponse, Link } from 'react-router'
function RouteError() {
const error = useRouteError()
if (isRouteErrorResponse(error) && error.status === 404) {
return <h1>Page not found</h1>
}
return (
<div role="alert">
<h1>Something went wrong</h1>
<p>Please try again, or go back.</p>
<Link to="/">Home</Link>
</div>
)
}
const router = createBrowserRouter([
{ path: '/', Component: Layout, ErrorBoundary: RouteError, children: [ /* ... */ ] },
])
A loader can signal a missing item by throwing a response, which the error screen turns into a 404 message (see Route Loaders and Lazy Routes). Unmatched URLs in data mode also reach the nearest error screen as a 404 response.
React Router also supports errorElement (an element instead of a component). For crashes in any other component, add an error boundary (Error Boundaries).
The 404 status problem in single-page apps
A static single-page app serves index.html with HTTP status 200 for every URL, even a page that does not exist. To a search engine this looks like a real page, which is called a soft 404. Real “not found” status codes need the server to know your routes, which is something frameworks with server rendering do. For a client-side app you can reduce the harm by:
- adding a
<meta name="robots" content="noindex">tag on your not-found page (React 19 can render<meta>from the component), - keeping the list of valid URLs in a sitemap,
- and using a framework if search visibility of many dynamic pages is important (Client-Side React vs React Frameworks).
Redirects for moved pages
When you rename a URL, keep the old one working with a redirect (<Navigate replace> as in Navigation), or better, configure a real HTTP 301 on your server so search engines learn the new address.
Key points
<Route path="*">catches unmatched URLs; show a clear page with a way back.- Also handle routes that match but whose data is missing.
- In data mode,
ErrorBoundary/errorElementplususeRouteErrorhandle route errors; remember soft 404s in client-only apps.