TanStack Router provides several built-in components for rendering routes, handling navigation, and managing errors.
Core Components
Outlet
Renders the child route’s component in a parent route layout.
import { Outlet } from '@tanstack/react-router'
const rootRoute = createRootRoute({
component: () => (
<div>
<header>My App Header</header>
<main>
<Outlet /> {/* Child routes render here */}
</main>
<footer>My App Footer</footer>
</div>
),
})
The Outlet component is the React Router equivalent of rendering child routes. It should be placed in parent route components where you want child routes to appear.
RouterProvider
Top-level component that renders the active route matches and provides the router to the React tree.
import { RouterProvider } from '@tanstack/react-router'
function App() {
return <RouterProvider router={router} />
}
Source: packages/react-router/src/RouterProvider.tsx:58-67
The router instance created with createRouter.
Additional options to update the router. Accepts same options as createRouter.
Navigation Components
Link
Strongly-typed anchor component for declarative navigation.
import { Link } from '@tanstack/react-router'
<Link to="/posts/$postId" params={{ postId: '123' }}>
View Post
</Link>
See the Link API documentation for full details.
Navigate
Component that triggers navigation when rendered.
import { Navigate } from '@tanstack/react-router'
function RedirectToLogin() {
return <Navigate to="/login" replace />
}
Source: packages/react-router/src/useNavigate.tsx:54-78
Path parameters for the destination.
Search parameters for the destination.
URL hash for the destination.
Replace current history entry instead of pushing.
Reset scroll position on navigation.
Renders nothing, navigation happens in an effect.
Matching Components
MatchRoute
Component that conditionally renders its children based on whether a route matches.
import { MatchRoute } from '@tanstack/react-router'
function Nav() {
return (
<nav>
<MatchRoute to="/posts" params={{ postId: '123' }}>
{(params) => <span>Viewing post {params?.postId}</span>}
</MatchRoute>
<MatchRoute to="/settings">
<SettingsIndicator />
</MatchRoute>
</nav>
)
}
Source: packages/react-router/src/Matches.tsx:201-216
Route path to match against.
Specific params to match.
Specific search params to match.
Allow fuzzy matching (partial path match).
Match against pending location instead of current.
Match paths case-sensitively.
children
React.ReactNode | (params) => React.ReactNode
Content to render when matched. If a function, receives the matched params.
Error Handling Components
CatchBoundary
Internal error boundary component used by routes to catch rendering errors.
import { CatchBoundary, ErrorComponent } from '@tanstack/react-router'
function MyRoute() {
return (
<CatchBoundary
getResetKey={() => resetKey}
errorComponent={MyErrorComponent}
onCatch={(error, errorInfo) => {
logError(error, errorInfo)
}}
>
<RouteContent />
</CatchBoundary>
)
}
Source: packages/react-router/src/CatchBoundary.tsx:5-29
getResetKey
() => number | string
required
Function returning a key that resets the error boundary when changed.
errorComponent
ErrorRouteComponent
default:"ErrorComponent"
Component to render when an error is caught.
onCatch
(error: Error, errorInfo: ErrorInfo) => void
Callback when an error is caught.
Content to render (protected by the boundary).
ErrorComponent
Default error component that displays error information.
import { ErrorComponent } from '@tanstack/react-router'
function MyErrorComponent({ error, reset }) {
return <ErrorComponent error={error} />
}
Source: packages/react-router/src/CatchBoundary.tsx:80-121
The error that was caught.
Function to reset the error boundary and retry.
The default ErrorComponent shows:
- Error message
- Toggle to show/hide details
- In development: full error details
- In production: minimal error message
CatchNotFound
Error boundary specifically for handling not-found errors.
import { CatchNotFound } from '@tanstack/react-router'
function Layout() {
return (
<CatchNotFound
fallback={(error) => (
<div>
<h1>404 - Page Not Found</h1>
<p>Path: {error.pathname}</p>
</div>
)}
>
<Outlet />
</CatchNotFound>
)
}
Source: packages/react-router/src/not-found.tsx:8-39
fallback
(error: NotFoundError) => React.ReactElement
Component to render when a not-found error is caught.
onCatch
(error: Error, errorInfo: ErrorInfo) => void
Callback when a not-found error is caught.
Content to render (protected by the boundary).
Blocker Components
Block
Component that blocks navigation based on a condition.
import { Block } from '@tanstack/react-router'
function EditForm() {
const [isDirty, setIsDirty] = useState(false)
return (
<>
<Block
shouldBlockFn={({ current, next }) => {
return isDirty && current.routeId !== next.routeId
}}
withResolver
>
{(resolver) => (
resolver.status === 'blocked' && (
<ConfirmDialog
onConfirm={resolver.proceed}
onCancel={resolver.reset}
/>
)
)}
</Block>
<form>...</form>
</>
)
}
Source: packages/react-router/src/useBlocker.tsx:286-306
shouldBlockFn
(args: BlockerArgs) => boolean | Promise<boolean>
required
Function to determine if navigation should be blocked.
enableBeforeUnload
boolean | (() => boolean)
default:"true"
Enable browser’s beforeunload warning.
Provide resolver to children for handling blocked navigation.
children
React.ReactNode | (resolver) => React.ReactNode
Content to render. If a function and withResolver is true, receives blocker resolver.
Usage Examples
Layout with Outlet
import { createRootRoute, Outlet } from '@tanstack/react-router'
const rootRoute = createRootRoute({
component: () => (
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/posts">Posts</Link>
</nav>
<main>
<Outlet /> {/* Child routes render here */}
</main>
<footer>
© 2024 My App
</footer>
</div>
),
})
Conditional Navigation
function ProtectedRoute() {
const { isAuthenticated } = useAuth()
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
return <DashboardContent />
}
Conditional Rendering
function Header() {
return (
<header>
<Logo />
<MatchRoute to="/posts" fuzzy>
<PostsNavigation />
</MatchRoute>
<MatchRoute to="/settings" fuzzy>
<SettingsNavigation />
</MatchRoute>
<MatchRoute to="/admin" fuzzy>
{(params) => <AdminBadge />}
</MatchRoute>
</header>
)
}
Custom Error Handling
import { CatchBoundary } from '@tanstack/react-router'
function CustomErrorComponent({ error, reset }) {
return (
<div className="error-container">
<h1>Oops! Something went wrong</h1>
<details>
<summary>Error Details</summary>
<pre>{error.message}</pre>
{error.stack && <pre>{error.stack}</pre>}
</details>
<button onClick={reset}>Try Again</button>
<Link to="/">Go Home</Link>
</div>
)
}
const rootRoute = createRootRoute({
component: () => (
<CatchBoundary
getResetKey={() => Date.now()}
errorComponent={CustomErrorComponent}
onCatch={(error) => {
// Log to error tracking service
logError(error)
}}
>
<Outlet />
</CatchBoundary>
),
})
Not Found Handling
import { CatchNotFound } from '@tanstack/react-router'
function Layout() {
return (
<div>
<Header />
<CatchNotFound
fallback={(error) => (
<div className="not-found">
<h1>404 - Page Not Found</h1>
<p>The page <code>{error.pathname}</code> does not exist.</p>
<Link to="/">Return Home</Link>
</div>
)}
>
<Outlet />
</CatchNotFound>
<Footer />
</div>
)
}
import { Block } from '@tanstack/react-router'
function ArticleEditor() {
const [content, setContent] = useState('')
const [saved, setSaved] = useState(true)
const handleChange = (value) => {
setContent(value)
setSaved(false)
}
const handleSave = async () => {
await saveArticle(content)
setSaved(true)
}
return (
<>
<Block
shouldBlockFn={() => !saved}
enableBeforeUnload
withResolver
>
{(resolver) => (
resolver.status === 'blocked' && (
<Modal>
<h2>Unsaved Changes</h2>
<p>You have unsaved changes. Are you sure you want to leave?</p>
<button onClick={handleSave}>
Save and Leave
</button>
<button onClick={resolver.proceed}>
Leave Without Saving
</button>
<button onClick={resolver.reset}>
Stay on Page
</button>
</Modal>
)
)}
</Block>
<Editor value={content} onChange={handleChange} />
<button onClick={handleSave} disabled={saved}>
{saved ? 'Saved' : 'Save'}
</button>
</>
)
}
Multi-Level Layout
const rootRoute = createRootRoute({
component: () => (
<div>
<GlobalHeader />
<Outlet />
<GlobalFooter />
</div>
),
})
const dashboardRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/dashboard',
component: () => (
<div>
<DashboardSidebar />
<main>
<Outlet /> {/* Nested dashboard routes */}
</main>
</div>
),
})
const settingsRoute = createRoute({
getParentRoute: () => dashboardRoute,
path: '/settings',
component: () => (
<div>
<SettingsTabs />
<Outlet /> {/* Settings sub-routes */}
</div>
),
})
Best Practices
Always Use Outlet
Every parent route that has children should render an Outlet:
// ✅ Good
const layoutRoute = createRoute({
component: () => (
<div>
<Header />
<Outlet /> {/* Children render here */}
<Footer />
</div>
),
})
// ❌ Bad - children won't render
const layoutRoute = createRoute({
component: () => (
<div>
<Header />
{/* Missing Outlet! */}
<Footer />
</div>
),
})
Error Boundaries at Strategic Levels
Place error boundaries at logical boundaries in your app:
// Root level - catches all errors
const rootRoute = createRootRoute({
errorComponent: GlobalErrorComponent,
})
// Feature level - catches feature-specific errors
const dashboardRoute = createRoute({
path: '/dashboard',
errorComponent: DashboardErrorComponent,
})
// Route level - catches route-specific errors
const settingsRoute = createRoute({
path: '/settings',
errorComponent: SettingsErrorComponent,
})
Use Navigate for Redirects
Use the Navigate component for declarative redirects:
function ProtectedPage() {
const { user } = useAuth()
if (!user) {
return (
<Navigate
to="/login"
search={{ redirect: window.location.pathname }}
replace
/>
)
}
return <PageContent />
}
Combine MatchRoute with Logic
Use MatchRoute for conditional UI based on routes:
function Layout() {
const matchRoute = useMatchRoute()
const showSidebar = matchRoute({ to: '/dashboard', fuzzy: true })
return (
<div>
{showSidebar && <Sidebar />}
<main>
<Outlet />
</main>
</div>
)
}
See Also