How toShow a 404 in React Router

Your app most likely will need to show a not found error screen in case the user type an incorrect URL. Or sometimes the URL is correct but the resource doesn't exists, e.g. an invalid post id.

Let's see a few ways to achieve this.

Using Error Boundaries

The first way is to rely on the route error boundaries.

The first one is the app/root.tsx error boundary, this will be used for any URL pathname that doesn't match any valid route, this works for the generic 404.

For the scenario of a matching route but missing resource, we could throw a 404 response from the loader.

routes/posts.$postId.tsx
export async function loader({ params }) { let post = await Post.find(params.postId) if (!post) throw data({ id: params.postId }, 404) // rest of the loader }

This will trigger the error boundary of the route, or if the route doesn't have an error boundary it will show the parent route error boundary, or the parent of that one, and that will keep until it finds an error boundary or reach the root route.

Using a Splat Route

A splat, or catch-all, route is a route that can receive any pathname, even nested ones. e.g. you could do /products/* and then /products/hostings/javascript will match the splat route and the route params will have ["hosting", "javascript"] as value.

routes/products.$.tsx
export async function loader({ params }) { console.log(params["*"]) // ["hosting", "javascript"] // code }

But this is not the only use-case for this type of routes, we could define a global splat route (/*) and use it to handle not found errors.

By registering the route.

app/routes.tsx
route("*", "routes/not-found.tsx")

Then in the route itself we can add a loader to set the correct status code.

routes/not-found.tsx
export async function loader() { // do things you may need here return data(null, 404) }

The loader is a normal loader so you could do any logic you need, like fetching some data to show in the error page.

Finally add a component to show the error message.

routes/not-found.tsx
export default function Component() { return <h1>404 - Not Found</h1> }

This way, our root route will not handle the 404, allowing us to keep rendering normally the root component, instead of using the error boundary.

This also allows us to use the root loader data even in the 404 page.

Now this works for the generic 404, but what about the 404 for a specific resource? We should keep using the error boundary for that. I recommend you to create a custom error boundary on the route that needs it, so you can show a more specific error message.