Skip to main content
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
router
TRouter
required
The router instance created with createRouter.
...rest
Partial<RouterOptions>
Additional options to update the router. Accepts same options as createRouter.
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. 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
to
string
required
Destination route path.
params
TParams
Path parameters for the destination.
Search parameters for the destination.
hash
string
URL hash for the destination.
replace
boolean
default:"false"
Replace current history entry instead of pushing.
resetScroll
boolean
default:"true"
Reset scroll position on navigation.
returns
null
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
to
string
required
Route path to match against.
params
TParams
Specific params to match.
search
TSearch
Specific search params to match.
fuzzy
boolean
default:"false"
Allow fuzzy matching (partial path match).
pending
boolean
default:"false"
Match against pending location instead of current.
caseSensitive
boolean
default:"false"
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.
children
React.ReactNode
required
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
error
Error
required
The error that was caught.
reset
() => void
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.
children
React.ReactNode
required
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.
disabled
boolean
default:"false"
Disable the blocker.
withResolver
boolean
default:"false"
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>
  )
}

Form with Navigation Blocker

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

Build docs developers (and LLMs) love