Skip to main content

React Route

React-specific route APIs for creating type-safe routes with components, hooks, and data loading.

createRoute

Create a standard route with React components.
import { createRoute } from '@tanstack/react-router'
import { rootRoute } from './root'

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  component: PostsComponent
})

function PostsComponent() {
  return <div>Posts</div>
}

createRootRoute

Create the root route for your application.
import { createRootRoute, Outlet } from '@tanstack/react-router'

export const rootRoute = createRootRoute({
  component: () => (
    <div>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/posts">Posts</Link>
      </nav>
      <hr />
      <Outlet />
    </div>
  )
})

createRootRouteWithContext

Create a root route with typed context.
import { createRootRouteWithContext } from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'

interface MyRouterContext {
  queryClient: QueryClient
  user: User | null
}

export const rootRoute = createRootRouteWithContext<MyRouterContext>()({
  component: RootComponent
})

// Now all child routes have access to typed context
const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  loader: async ({ context }) => {
    // context.queryClient is fully typed
    const posts = await context.queryClient.fetchQuery(...)
    return { posts }
  }
})

createFileRoute

Create a route from a file-based routing setup.
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => {
    const post = await fetchPost(params.postId)
    return { post }
  },
  component: PostComponent
})

function PostComponent() {
  const { post } = Route.useLoaderData()
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

Route Options (React)

All options from core Route API plus React-specific options.
component
() => JSX.Element
React component to render for this route.
component: () => <PostsList />
errorComponent
({ error, reset }) => JSX.Element
Component to render when an error occurs.
errorComponent: ({ error, reset }) => (
  <div>
    <h1>Error: {error.message}</h1>
    <button onClick={reset}>Try Again</button>
  </div>
)
pendingComponent
() => JSX.Element
Component to render while loading.
pendingComponent: () => <Spinner />
notFoundComponent
() => JSX.Element
Component to render when no child route matches.

Route Hooks

Type-safe hooks for accessing route data.

Route.useLoaderData()

Access loader data with full type safety.
const postRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts/$postId',
  loader: async ({ params }) => {
    const post = await fetchPost(params.postId)
    return { post }
  },
  component: () => {
    const { post } = postRoute.useLoaderData()
    //      ^? { post: Post }
    return <h1>{post.title}</h1>
  }
})

Route.useParams()

Access route params with type safety.
const postRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts/$postId',
  component: () => {
    const { postId } = postRoute.useParams()
    //      ^? { postId: string }
    return <div>Post ID: {postId}</div>
  }
})

Route.useSearch()

Access validated search params.
import { z } from 'zod'

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  validateSearch: z.object({
    page: z.number().default(1),
    filter: z.string().optional()
  }),
  component: () => {
    const { page, filter } = postsRoute.useSearch()
    //      ^? { page: number, filter?: string }
    return <div>Page {page}</div>
  }
})

Route.useRouteContext()

Access route context.
const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  context: () => ({
    title: 'Posts'
  }),
  component: () => {
    const context = postsRoute.useRouteContext()
    return <h1>{context.title}</h1>
  }
})

Route.useNavigate()

Get a navigate function scoped to the route.
const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  component: () => {
    const navigate = postsRoute.useNavigate()
    
    return (
      <button onClick={() => navigate({ to: '/posts/$postId', params: { postId: '1' } })}>
        View Post
      </button>
    )
  }
})

Route.useLoaderDeps()

Access loader dependencies.
const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  loaderDeps: ({ search }) => ({
    page: search.page
  }),
  component: () => {
    const deps = postsRoute.useLoaderDeps()
    return <div>Current page: {deps.page}</div>
  }
})

Lazy Route Loading

Split routes into separate chunks for code splitting.

createLazyRoute

// posts.lazy.tsx
import { createLazyRoute } from '@tanstack/react-router'

export const Route = createLazyRoute('/posts')({
  component: PostsComponent
})

function PostsComponent() {
  return <div>Posts (lazy loaded)</div>
}

createLazyFileRoute

// posts.$postId.lazy.tsx
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/posts/$postId')({
  component: PostComponent
})

function PostComponent() {
  const { post } = Route.useLoaderData()
  return <div>{post.title}</div>
}

Route Definition with Lazy Loading

// posts.$postId.tsx (route definition)
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => {
    const post = await fetchPost(params.postId)
    return { post }
  },
  // Component will be loaded from lazy file
})

// posts.$postId.lazy.tsx (lazy component)
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/posts/$postId')({
  component: PostComponent
})

function PostComponent() {
  const { post } = Route.useLoaderData()
  return <article>{post.title}</article>
}

lazyRouteComponent

Lazily load a single route component.
import { lazyRouteComponent } from '@tanstack/react-router'

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  component: lazyRouteComponent(
    () => import('./components/Posts'),
    'Posts'
  )
})

RouteApi

Access route APIs without direct route reference.
import { RouteApi } from '@tanstack/react-router'

const routeApi = new RouteApi({ id: '/posts/$postId' })

function PostComponent() {
  const { post } = routeApi.useLoaderData()
  const { postId } = routeApi.useParams()
  
  return <div>{post.title}</div>
}

getRouteApi

Get a cached RouteApi instance.
import { getRouteApi } from '@tanstack/react-router'

const postRoute = getRouteApi('/posts/$postId')

function PostComponent() {
  const { post } = postRoute.useLoaderData()
  return <div>{post.title}</div>
}

Example: Complete Route Setup

// routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'

export const Route = createRootRoute({
  component: () => (
    <div>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/posts">Posts</Link>
      </nav>
      <Outlet />
    </div>
  )
})

// routes/posts.$postId.tsx
import { createFileRoute, notFound } from '@tanstack/react-router'
import { z } from 'zod'

export const Route = createFileRoute('/posts/$postId')({
  // Validate params
  params: {
    parse: (params) => ({
      postId: z.number().int().parse(Number(params.postId))
    }),
    stringify: (params) => ({
      postId: String(params.postId)
    })
  },
  
  // Validate search
  validateSearch: z.object({
    tab: z.enum(['overview', 'comments']).default('overview')
  }),
  
  // Load data
  loader: async ({ params, abortController }) => {
    const post = await fetchPost(params.postId, {
      signal: abortController.signal
    })
    
    if (!post) {
      throw notFound()
    }
    
    return { post }
  },
  
  // Error component
  errorComponent: ({ error, reset }) => (
    <div>
      <h1>Error loading post</h1>
      <pre>{error.message}</pre>
      <button onClick={reset}>Retry</button>
    </div>
  ),
  
  // Pending component
  pendingComponent: () => (
    <div className="animate-pulse">
      Loading post...
    </div>
  )
})

// Lazy component in separate file
// routes/posts.$postId.lazy.tsx
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/posts/$postId')({
  component: PostComponent
})

function PostComponent() {
  const { post } = Route.useLoaderData()
  const { tab } = Route.useSearch()
  const navigate = Route.useNavigate()
  
  return (
    <article>
      <h1>{post.title}</h1>
      
      <div className="tabs">
        <button 
          onClick={() => navigate({ search: { tab: 'overview' } })}
          className={tab === 'overview' ? 'active' : ''}
        >
          Overview
        </button>
        <button 
          onClick={() => navigate({ search: { tab: 'comments' } })}
          className={tab === 'comments' ? 'active' : ''}
        >
          Comments
        </button>
      </div>
      
      <div>
        {tab === 'overview' ? (
          <div>{post.content}</div>
        ) : (
          <Comments postId={post.id} />
        )}
      </div>
    </article>
  )
}

Build docs developers (and LLMs) love