---
name: router-core/not-found-and-errors
description: >-
notFound() function, notFoundComponent, defaultNotFoundComponent,
notFoundMode (fuzzy/root), errorComponent, CatchBoundary,
CatchNotFound, isNotFound, NotFoundRoute (deprecated), route
masking (mask option, createRouteMask, unmaskOnReload).
type: sub-skill
library: tanstack-router
library_version: '1.166.2'
requires:
- router-core
sources:
- TanStack/router:docs/router/guide/not-found-errors.md
- TanStack/router:docs/router/guide/route-masking.md
---
# Not Found and Errors
TanStack Router handles two categories of "not found": unmatched URL paths (automatic) and missing resources like a post that doesn't exist (manual via `notFound()`). Error boundaries are configured per-route via `errorComponent`.
> **CRITICAL**: Do NOT use the deprecated `NotFoundRoute`. When present, `notFound()` and `notFoundComponent` will NOT work. Remove it and use `notFoundComponent` instead.
> **CRITICAL**: `useLoaderData` may be undefined inside `notFoundComponent`. Use `useParams`, `useSearch`, or `useRouteContext` instead.
## Not Found Handling
### Global 404: `notFoundComponent` on Root Route
```tsx
// src/routes/__root.tsx
import { createRootRoute, Outlet, Link } from '@tanstack/react-router'
export const Route = createRootRoute({
component: () =>
Not found!
Go homePost "{postId}" not found
}, }) function PostComponent() { const { post } = Route.useLoaderData() returnError: {error.message}
Something went wrong: {error.message}
Item {params.id} not found
} ``` To forward partial data, use the `data` option on `notFound()`: ```tsx loader: async ({ params }) => { const partialData = await getPartialData(params.id) if (!partialData.fullResource) { throw notFound({ data: { name: partialData.name } }) } return partialData }, notFoundComponent: ({ data }) => { // data is typed as unknown — validate it const info = data as { name: string } | undefined return{info?.name ?? 'Resource'} not found
}, ``` ## Route Masking Route masking shows a different URL in the browser bar than the actual route being rendered. Masking data is stored in `location.state` and is lost when the URL is shared or opened in a new tab. ### Imperative Masking on `` ```tsx import { Link } from '@tanstack/react-router' function PhotoGrid({ photoId }: { photoId: string }) { return ( Open Photo ) } ``` ### Imperative Masking with `useNavigate` ```tsx import { useNavigate } from '@tanstack/react-router' function OpenPhotoButton({ photoId }: { photoId: string }) { const navigate = useNavigate() return ( ) } ``` ### Declarative Masking with `createRouteMask` ```tsx import { createRouter, createRouteMask } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' const photoModalMask = createRouteMask({ routeTree, from: '/photos/$photoId/modal', to: '/photos/$photoId', params: (prev) => ({ photoId: prev.photoId }), }) const router = createRouter({ routeTree, routeMasks: [photoModalMask], }) ``` ### Unmasking on Reload By default, masks survive local page reloads. To unmask on reload: ```tsx // Per-mask const mask = createRouteMask({ routeTree, from: '/photos/$photoId/modal', to: '/photos/$photoId', params: (prev) => ({ photoId: prev.photoId }), unmaskOnReload: true, }) // Per-link Open Photo // Router-wide default const router = createRouter({ routeTree, unmaskOnReload: true, }) ``` ## Common Mistakes ### 1. HIGH: Using deprecated `NotFoundRoute` ```tsx // WRONG — NotFoundRoute blocks notFound() and notFoundComponent from working import { NotFoundRoute } from '@tanstack/react-router' const notFoundRoute = new NotFoundRoute({ component: () =>404
}) const router = createRouter({ routeTree, notFoundRoute }) // CORRECT — use notFoundComponent on root route export const Route = createRootRoute({ component: () =>404
, }) ``` ### 2. MEDIUM: Expecting `useLoaderData` in `notFoundComponent` ```tsx // WRONG — loader may not have completed notFoundComponent: () => { const data = Route.useLoaderData() // may be undefined! return{data.title} not found
} // CORRECT — use safe hooks notFoundComponent: () => { const { postId } = Route.useParams() returnPost {postId} not found
} ``` ### 3. MEDIUM: Leaf routes cannot handle not-found errors Only routes with children (and therefore an `Not found
, }) ``` ### 4. MEDIUM: Expecting masked URLs to survive sharing Masking data lives in `location.state` (browser history). When a masked URL is copied, shared, or opened in a new tab, the masking data is lost. The browser navigates to the visible (masked) URL directly. ### 5. HIGH (cross-skill): Using `reset()` alone instead of `router.invalidate()` ```tsx // WRONG — reset() clears the error boundary but does NOT re-run the loader function ErrorFallback({ error, reset }: { error: Error; reset: () => void }) { return } // CORRECT — invalidate re-runs loaders and resets the error boundary function ErrorFallback({ error }: { error: Error; reset: () => void }) { const router = useRouter() return ( ) } ``` ## Cross-References - **router-core/data-loading** — `notFound()` thrown in loaders interacts with error boundaries and loader data availability. `errorComponent` retry requires `router.invalidate()`. - **router-core/type-safety** — `notFoundComponent` data is typed as `unknown`; validate before use.