⚠️ This page covers the newer
notFound
function andnotFoundComponent
API for handling not found errors. TheNotFoundRoute
route is deprecated and will be removed in a future release. See Migrating fromNotFoundRoute
for more information.
There are 2 uses for not-found errors in TanStack Router:
notFoundMode
is set to fuzzy
, the nearest parent route with a notFoundComponent
will handle the error. If the router's notFoundMode
is set to root
, the root route will handle the error./users
when there is no /users
route/posts/1/edit
when the route tree only handles /posts/$postId
beforeLoad
or loader
functions using the notFound
utility.notFoundComponent
or the root route/posts/1
when the post with ID 1 does not exist/docs/path/to/document
when the document does not existUnder the hood, both of these cases are implemented using the same notFound
function and notFoundComponent
API.
notFoundMode
optionWhen TanStack Router encounters a pathname that doesn't match any known route pattern OR partially matches a route pattern but with extra trailing pathname segments, it will automatically throw a not-found error.
Depending on the notFoundMode
option, the router will handle these automatic errors differently::
notFoundComponent
.notFoundComponent
, regardless of the nearest matching route.notFoundMode: 'fuzzy'
By default, the router's notFoundMode
is set to fuzzy
, which indicates that if a pathname doesn't match any known route, the router will attempt to use the closest matching route with children/(an outlet) and a configured not found component.
❓ Why is this the default? Fuzzy matching to preserve as much parent layout as possible for the user gives them more context to navigate to a useful location based on where they thought they would arrive.
The nearest suitable route is found using the following criteria:
Outlet
to render the notFoundComponent
notFoundComponent
configured or the router must have a defaultNotFoundComponent
configuredFor example, consider the following route tree:
__root__
(has a notFoundComponent
configured)
posts
(has a notFoundComponent
configured)
$postId
(has a notFoundComponent
configured)If provided the path of /posts/1/edit
, the following component structure will be rendered:
<Root>
<Posts>
<Posts.notFoundComponent>
The notFoundComponent
of the posts
route will be rendered because it is the nearest suitable parent route with children (and therefore an outlet) and a notFoundComponent
configured.
notFoundMode: 'root'
When notFoundMode
is set to root
, all not-found errors will be handled by the root route's notFoundComponent
instead of bubbling up from the nearest fuzzy-matched route.
For example, consider the following route tree:
__root__
(has a notFoundComponent
configured)
posts
(has a notFoundComponent
configured)
$postId
(has a notFoundComponent
configured)If provided the path of /posts/1/edit
, the following component structure will be rendered:
<Root>
<Root.notFoundComponent>
The notFoundComponent
of the __root__
route will be rendered because the notFoundMode
is set to root
.
notFoundComponent
To handle both types of not-found errors, you can attach a notFoundComponent
to a route. This component will be rendered when a not-found error is thrown.
For example, configuring a notFoundComponent
for a /settings
route to handle non-existing settings pages:
export const Route = createFileRoute('/settings')({ component: () => { return ( <div> <p>Settings page</p> <Outlet /> </div> ) }, notFoundComponent: () => { return <p>This setting page doesn't exist!</p> },})
Or configuring a notFoundComponent
for a /posts/$postId
route to handle posts that don't exist:
export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params: { postId } }) => { const post = await getPost(postId) if (!post) throw notFound() return { post } }, component: ({ post }) => { return ( <div> <h1>{post.title}</h1> <p>{post.body}</p> </div> ) }, notFoundComponent: () => { return <p>Post not found!</p> },})
You may want to provide a default not-found component for every route in your app with child routes.
Why only routes with children? Leaf-node routes (routes without children) will never render an
Outlet
and therefore are not able to handle not-found errors.
To do this, pass a defaultNotFoundComponent
to the createRouter
function:
const router = createRouter({ defaultNotFoundComponent: () => { return ( <div> <p>Not found!</p> <Link to="/">Go home</Link> </div> ) },})
notFound
errorsYou can manually throw not-found errors in loader methods and components using the notFound
function. This is useful when you need to signal that a resource cannot be found.
The notFound
function works in a similar fashion to the redirect
function. To cause a not-found error, you can throw a notFound()
.
export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params: { postId } }) => { // Returns `null` if the post doesn't exist const post = await getPost(postId) if (!post) { throw notFound() // Alternatively, you can make the notFound function throw: // notFound({ throw: true }) } // Post is guaranteed to be defined here because we threw an error return { post } },})
The not-found error above will be handled by the same route or nearest parent route that has either a notFoundComponent
route option or the defaultNotFoundComponent
router option configured.
If neither the route nor any suitable parent route is found to handle the error, the root route will handle it using TanStack Router's extremely basic (and purposefully undesirable) default not-found component that simply renders <div>Not Found</div>
. It's highly recommended to either attach at least one notFoundComponent
to the root route or configure a router-wide defaultNotFoundComponent
to handle not-found errors.
Sometimes you may want to trigger a not-found on a specific parent route and bypass the normal not-found component propagation. To do this, pass in a route id to the route
option in the notFound
function.
// _layout.tsxexport const Route = createFileRoute('/_layout')({ // This will render notFoundComponent: () => { return <p>Not found (in _layout)</p> }, component: () => { return ( <div> <p>This is a layout!</p> <Outlet /> </div> ) },})
// _layout/a.tsxexport const Route = createFileRoute('/_layout/a')({ loader: async () => { // This will make LayoutRoute handle the not-found error throw notFound({ routeId: '/_layout' }) // ^^^^^^^^^ This will autocomplete from the registered router }, // This WILL NOT render notFoundComponent: () => { return <p>Not found (in _layout/a)</p> },})
You can also target the root route by passing the exported rootRouteId
variable to the notFound
function's route
property:
import { rootRouteId } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params: { postId } }) => { const post = await getPost(postId) if (!post) throw notFound({ routeId: rootRouteId }) return { post } },})
You can also throw not-found errors in components. However, it is recommended to throw not-found errors in loader methods instead of components in order to correctly type loader data and prevent flickering.
TanStack Router exposes a CatchNotFound
component similar to CatchBoundary
that can be used to catch not-found errors in components and display UI accordingly.
notFoundComponent
notFoundComponent
is a special case when it comes to data loading. SomeRoute.useLoaderData
may not be defined depending on which route you are trying to access and where the not-found error gets thrown. However, Route.useParams
, Route.useSearch
, Route.useRouteContext
, etc. will return a defined value.
If you need to pass incomplete loader data to notFoundComponent
, pass the data via the data
option in the notFound
function and validate it in notFoundComponent
.
export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params: { postId } }) => { const post = await getPost(postId) if (!post) throw notFound({ // Forward some data to the notFoundComponent // data: someIncompleteLoaderData }) return { post } }, // `data: unknown` is passed to the component via the `data` option when calling `notFound` notFoundComponent: ({ data }) => { // ❌ useLoaderData is not valid here: const { post } = Route.useLoaderData()
// ✅: const { postId } = Route.useParams() const search = Route.useSearch() const context = Route.useRouteContext()
return <p>Post with id {postId} not found!</p> },})
See SSR guide for more information.
NotFoundRoute
The NotFoundRoute
API is deprecated in favor of notFoundComponent
. The NotFoundRoute
API will be removed in a future release.
The notFound
function and notFoundComponent
will not work when using NotFoundRoute
.
The main differences are:
NotFoundRoute
is a route that requires an <Outlet>
on its parent route to render. notFoundComponent
is a component that can be attached to any route.NotFoundRoute
, you can't use layouts. notFoundComponent
can be used with layouts.notFoundComponent
, path matching is strict. This means that if you have a route at /post/$postId
, a not-found error will be thrown if you try to access /post/1/2/3
. With NotFoundRoute
, /post/1/2/3
would match the NotFoundRoute
and only render it if there is an <Outlet>
.To migrate from NotFoundRoute
to notFoundComponent
, follow these steps:
NotFoundRoute
with notFoundComponent
s on the routes that need to handle not-found errors. For "global" not-found errors, attach a notFoundComponent
to the root route.<Outlet>
s from the routes that used NotFoundRoute
.Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.