
not-found-and-errors
notFound() function, notFoundComponent, defaultNotFoundComponent, notFoundMode (fuzzy/root), errorComponent, CatchBoundary, CatchNotFound, isNotFound, NotFoundRoute (deprecated), route masking (mask option, createRouteMask, unmaskOnReload).
- Installs
- 197
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/tanstack/router --skill not-found-and-errors- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads not-found-and-errors automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
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()andnotFoundComponentwill NOT work. Remove it and usenotFoundComponentinstead. CRITICAL:useLoaderDatamay be undefined insidenotFoundComponent. UseuseParams,useSearch, oruseRouteContextinstead.
Not Found Handling
Global 404: notFoundComponent on Root Route
// src/routes/__root.tsx
import { createRootRoute, Outlet, Link } from '@tanstack/react-router'
export const Route = createRootRoute({
component: () => <Outlet />,
notFoundComponent: () => {
return (
<div>
<h1>404 — Page Not Found</h1>
<Link to="/">Go Home</Link>
</div>
)
},
})
Router-Wide Default: defaultNotFoundComponent
// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({
routeTree,
defaultNotFoundComponent: () => {
return (
<div>
<p>Not found!</p>
<Link to="/">Go home</Link>
</div>
)
},
})
Per-Route 404: Missing Resources with notFound()
Throw notFound() in loader or beforeLoad when a resource doesn't exist. It works like redirect() — throw it to trigger the not-found boundary.
// src/routes/posts.$postId.tsx
import { createFileRoute, notFound } from '@tanstack/react-router'
import { getPost } from '../api'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params: { postId } }) => {
const post = await getPost(postId)
if (!post) throw notFound()
return { post }
},
component: PostComponent,
notFoundComponent: ({ data }) => {
const { postId } = Route.useParams()
return <p>Post "{postId}" not found</p>
},
})
function PostComponent() {
const { post } = Route.useLoaderData()
return <h1>{post.title}</h1>
}
Targeting a Specific Route with notFound({ routeId })
You can force a specific parent route to handle the not-found error:
// src/routes/_layout/posts.$postId.tsx
import { createFileRoute, notFound } from '@tanstack/react-router'
export const Route = createFileRoute('/_layout/posts/$postId')({
loader: async ({ params: { postId } }) => {
const post = await getPost(postId)
if (!post) throw notFound({ routeId: '/_layout' })
return { post }
},
})
Targeting Root Route with rootRouteId
import { createFileRoute, notFound, 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 }
},
})
notFoundMode: Fuzzy vs Root
fuzzy (default)
The router finds the nearest parent route with children and a notFoundComponent. Preserves as much parent layout as possible.
Given routes: __root__ → posts → $postId, accessing /posts/1/edit:
<Root>renders<Posts>renders<Posts.notFoundComponent>renders (nearest parent with children + notFoundComponent)
root
All not-found errors go to the root route's notFoundComponent, regardless of matching:
const router = createRouter({
routeTree,
notFoundMode: 'root',
})
Error Handling
errorComponent Per Route
errorComponent receives error, info, and reset props. For loader errors, use router.invalidate() to re-run the loader — it automatically resets the error boundary.
// src/routes/posts.$postId.tsx
import { createFileRoute, useRouter } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params: { postId } }) => {
const res = await fetch(`/api/posts/${postId}`)
if (!res.ok) throw new Error('Failed to load post')
return res.json()
},
component: PostComponent,
errorComponent: PostErrorComponent,
})
function PostErrorComponent({
error,
}: {
error: unknown
info?: { componentStack: string }
reset: () => void
}) {
const router = useRouter()
return (
<div>
<p>Error: {error instanceof Error ? error.message : String(error)}</p>
<button
onClick={() => {
// Invalidate re-runs the loader and resets the error boundary
router.invalidate()
}}
>
Retry
</button>
</div>
)
}
function PostComponent() {
const data = Route.useLoaderData()
return <h1>{data.title}</h1>
}
Router-Wide Default Error Component
const router = createRouter({
routeTree,
defaultErrorComponent: ({ error }) => {
const router = useRouter()
return (
<div>
<p>
Something went wrong:{' '}
{error instanceof Error ? error.message : String(error)}
</p>
<button
onClick={() => {
router.invalidate()
}}
>
Retry
</button>
</div>
)
},
})
Data in notFoundComponent
notFoundComponent cannot reliably use useLoaderData because the loader may not have completed. Safe hooks:
notFoundComponent: ({ data }) => {
// SAFE — always available:
const params = Route.useParams()
const search = Route.useSearch()
const context = Route.useRouteContext()
// UNSAFE — may be undefined:
// const loaderData = Route.useLoaderData()
return <p>Item {params.id} not found</p>
}
To forward partial data, use the data option on notFound():
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 <p>{info?.name ?? 'Resource'} not found</p>
},
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 <Link>
import { Link } from '@tanstack/react-router'
function PhotoGrid({ photoId }: { photoId: string }) {
return (
<Link
to="/photos/$photoId/modal"
params={{ photoId }}
mask={{
to: '/photos/$photoId',
params: { photoId },
}}
>
Open Photo
</Link>
)
}
Imperative Masking with useNavigate
import { useNavigate } from '@tanstack/react-router'
function OpenPhotoButton({ photoId }: { photoId: string }) {
const navigate = useNavigate()
return (
<button
onClick={() =>
navigate({
to: '/photos/$photoId/modal',
params: { photoId },
mask: {
to: '/photos/$photoId',
params: { photoId },
},
})
}
>
Open Photo
</button>
)
}
Declarative Masking with createRouteMask
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:
// Per-mask
const mask = createRouteMask({
routeTree,
from: '/photos/$photoId/modal',
to: '/photos/$photoId',
params: (prev) => ({ photoId: prev.photoId }),
unmaskOnReload: true,
})
// Per-link
<Link
to="/photos/$photoId/modal"
params={{ photoId }}
mask={{ to: '/photos/$photoId', params: { photoId } }}
unmaskOnReload
>
Open Photo
</Link>
// Router-wide default
const router = createRouter({
routeTree,
unmaskOnReload: true,
})
Common Mistakes
1. HIGH: Using deprecated NotFoundRoute
// WRONG — NotFoundRoute blocks notFound() and notFoundComponent from working
import { NotFoundRoute } from '@tanstack/react-router'
const notFoundRoute = new NotFoundRoute({ component: () => <p>404</p> })
const router = createRouter({ routeTree, notFoundRoute })
// CORRECT — use notFoundComponent on root route
export const Route = createRootRoute({
component: () => <Outlet />,
notFoundComponent: () => <p>404</p>,
})
2. MEDIUM: Expecting useLoaderData in notFoundComponent
// WRONG — loader may not have completed
notFoundComponent: () => {
const data = Route.useLoaderData() // may be undefined!
return <p>{data.title} not found</p>
}
// CORRECT — use safe hooks
notFoundComponent: () => {
const { postId } = Route.useParams()
return <p>Post {postId} not found</p>
}
3. MEDIUM: Leaf routes cannot handle not-found errors
Only routes with children (and therefore an <Outlet>) can render notFoundComponent. Leaf routes (routes without children) will never catch not-found errors — the error bubbles up to the nearest parent with children.
// This route has NO children — notFoundComponent here will not catch
// unmatched child paths (there are no child paths to unmatch)
export const Route = createFileRoute('/posts/$postId')({
// notFoundComponent here only works for notFound() thrown in THIS route's loader
// It does NOT catch path-based not-founds
notFoundComponent: () => <p>Not found</p>,
})
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()
// WRONG — reset() clears the error boundary but does NOT re-run the loader
function ErrorFallback({
error,
reset,
}: {
error: unknown
reset: () => void
}) {
return <button onClick={reset}>Retry</button>
}
// CORRECT — invalidate re-runs loaders and resets the error boundary
function ErrorFallback({ error }: { error: unknown; reset: () => void }) {
const router = useRouter()
return (
<button
onClick={() => {
router.invalidate()
}}
>
Retry
</button>
)
}
Cross-References
- router-core/data-loading —
notFound()thrown in loaders interacts with error boundaries and loader data availability.errorComponentretry requiresrouter.invalidate(). - router-core/type-safety —
notFoundComponentdata is typed asunknown; validate before use.
FAQ
What does the not-found-and-errors skill do?
notFound() function, notFoundComponent, defaultNotFoundComponent, notFoundMode (fuzzy/root), errorComponent, CatchBoundary, CatchNotFound, isNotFound, NotFoundRoute (deprecated), route masking (mask option, createRouteMask, unmaskOnReload).
How do I install not-found-and-errors?
Run npx skills add https://github.com/tanstack/router --skill not-found-and-errors in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/not-found-and-errors/ (personal) or .claude/skills/not-found-and-errors/ (project).
Which AI agents can use not-found-and-errors?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is not-found-and-errors safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains not-found-and-errors?
It is published by tanstack in tanstack/router. It has 197 recorded installs.
More from tanstack/router
router-core
tanstack/router
Framework-agnostic core concepts for TanStack Router: route trees, createRouter, createRoute, createRootRoute, createRootRouteWithContext, addChildren, Register type declaration, route matching, route sorting, file naming conventions. Entry point for all router skills.
react-router
tanstack/router
React bindings for TanStack Router: RouterProvider, useRouter, useRouterState, useMatch, useMatches, useLocation, useSearch, useParams, useNavigate, useLoaderData, useLoaderDeps, useRouteContext, useBlocker, useCanGoBack, Link, Navigate, Outlet, CatchBoundary, ErrorComponent. React-specific patterns for hooks, providers, SSR hydration, and createLink with forwardRef.
react-start
tanstack/router
React bindings for TanStack Start: createStart, StartClient, StartServer, React-specific imports, re-exports from @tanstack/react-router, full project setup with React, useServerFn hook.
start-core
tanstack/router
Core overview for TanStack Start: tanstackStart() Vite plugin, getRouter() factory, root route document shell (HeadContent, Scripts, Outlet), client/server entry points, routeTree.gen.ts, tsconfig configuration. Entry point for all Start skills.
router-query
tanstack/router
Integrating TanStack Router with TanStack Query: queryClient in router context, static queries and fire-and-forget queries in loaders, useSuspenseQuery in components, defaultPreloadStaleTime: 0, setupRouterSsrQueryIntegration for SSR dehydration/hydration and streaming, per-request QueryClient isolation.
start-server-core
tanstack/router
Server-side runtime for TanStack Start: createStartHandler, request/response utilities (getRequest, setResponseHeader, setCookie, getCookie, appendResponseHeader), three-phase request handling, AsyncLocalStorage context.
Popular other skills
hyperframes-cli
heygen-com/hyperframes
The HyperFrames CLI by job - scaffold, check, look at frames, edit the timeline, preview, render locally or in the cloud, publish, project history - plus reading its JSON and recovering from build or render failures.
triage
mattpocock/skills
Move issues and external PRs through a state machine of triage roles, categorise, verify, grill if needed, and write agent-ready briefs.
ask-matt
mattpocock/skills
Ask which skill or flow fits your situation. A router over the skills in this repo.
to-questionnaire
mattpocock/skills
Turn a decision you can't fully answer into a questionnaire for someone else to fill in.
loop-me
mattpocock/skills
Grill me about specs for the workflows I want to build, within this workspace.