This quickstart guide will help you create a fully functional router-based React application in under 5 minutes.
Prerequisites
Before starting, make sure you have:
- Node.js 20.19 or higher installed
- A React application set up (or create one with Vite)
- TanStack Router installed (see Installation)
Step 1: Create Your First Routes
Let’s build a simple application with a home page and an about page using code-based routing.
Import the Router Dependencies
Start by importing the necessary functions from TanStack Router:
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
Link,
Outlet,
RouterProvider,
createRootRoute,
createRoute,
createRouter,
} from '@tanstack/react-router'
The root route serves as the layout wrapper for all other routes. It typically contains navigation and an <Outlet /> where child routes render:
const rootRoute = createRootRoute({
component: () => (
<>
<div className="p-2 flex gap-2">
<Link to="/" className="[&.active]:font-bold">
Home
</Link>
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
</div>
<hr />
<Outlet />
</>
),
})
The <Outlet /> component renders the matched child route’s component. This enables nested layouts.
The index route renders at the root path (/):
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: function Index() {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
</div>
)
},
})
Add an about page at /about:
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/about',
component: function About() {
return <div className="p-2">Hello from About!</div>
},
})
Combine your routes into a tree structure:
const routeTree = rootRoute.addChildren([indexRoute, aboutRoute])
Create the Router Instance
Create the router with your route tree and optional configuration:
const router = createRouter({
routeTree,
defaultPreload: 'intent',
scrollRestoration: true,
})
The defaultPreload: 'intent' option preloads routes when users hover over links, creating a snappier experience.
Register the Router for Type-Safety
Register your router type for full TypeScript inference:
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
This declaration enables autocomplete for route paths, params, and search parameters throughout your application.
Finally, render your app with the RouterProvider:
const rootElement = document.getElementById('app')!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(<RouterProvider router={router} />)
}
Complete Example
Here’s the full code for reference:
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
Link,
Outlet,
RouterProvider,
createRootRoute,
createRoute,
createRouter,
} from '@tanstack/react-router'
const rootRoute = createRootRoute({
component: () => (
<>
<div className="p-2 flex gap-2">
<Link to="/" className="[&.active]:font-bold">
Home
</Link>
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
</div>
<hr />
<Outlet />
</>
),
})
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: function Index() {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
</div>
)
},
})
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/about',
component: function About() {
return <div className="p-2">Hello from About!</div>
},
})
const routeTree = rootRoute.addChildren([indexRoute, aboutRoute])
const router = createRouter({
routeTree,
defaultPreload: 'intent',
scrollRestoration: true,
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const rootElement = document.getElementById('app')!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(<RouterProvider router={router} />)
}
Step 2: Add Data Loading
TanStack Router can load data before rendering routes. Let’s add a loader to fetch posts:
import { createRoute } from '@tanstack/react-router'
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/posts',
loader: async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts')
return response.json()
},
component: function Posts() {
const posts = postsRoute.useLoaderData()
return (
<div className="p-2">
<h3>Posts</h3>
<ul>
{posts.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
},
})
Loaders run before the route component renders, ensuring data is ready when your component mounts.
Step 3: Add Path Parameters
Create dynamic routes with type-safe path parameters:
const postRoute = createRoute({
getParentRoute: () => postsRoute,
path: '$postId',
loader: async ({ params }) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.postId}`
)
return response.json()
},
component: function Post() {
const post = postRoute.useLoaderData()
return (
<div className="p-2">
<h4>{post.title}</h4>
<p>{post.body}</p>
</div>
)
},
})
Install and add the DevTools for debugging:
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
const rootRoute = createRootRoute({
component: () => (
<>
<div className="p-2 flex gap-2">
{/* navigation */}
</div>
<hr />
<Outlet />
<TanStackRouterDevtools position="bottom-right" />
</>
),
})
DevTools are automatically tree-shaken in production builds, so you can safely include them in your root route.
Type-Safe Navigation
TanStack Router provides full type-safety for navigation:
import { Link, useNavigate } from '@tanstack/react-router'
function MyComponent() {
const navigate = useNavigate()
return (
<div>
{/* Type-safe Link component */}
<Link to="/posts/$postId" params={{ postId: '1' }}>
View Post 1
</Link>
{/* Programmatic navigation */}
<button
onClick={() => {
navigate({ to: '/posts/$postId', params: { postId: '2' } })
}}
>
Navigate to Post 2
</button>
</div>
)
}
Next Steps
Congratulations! You’ve built your first TanStack Router application. Here’s what to explore next:
Core Concepts
Learn the fundamentals of routing, navigation, and data loading
File-Based Routing
Organize routes by file structure with automatic generation
Search Parameters
Master type-safe URL search parameters as application state
Error Handling
Implement robust error boundaries and fallbacks
Common Patterns
Route-Level Code Splitting
Lazy load route components for better performance:
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/about',
}).lazy(() => import('./routes/about.lazy').then((d) => d.Route))
Search Parameter Validation
Add type-safe search params with validation:
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'
const searchRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/search',
validateSearch: zodValidator({
schema: z.object({
query: z.string().optional(),
page: z.number().default(1),
}),
}),
component: function Search() {
const { query, page } = searchRoute.useSearch()
return <div>Searching for: {query} (page {page})</div>
},
})
Nested Layouts
Create layouts that wrap multiple child routes:
const dashboardRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/dashboard',
component: () => (
<div className="dashboard-layout">
<aside>Sidebar</aside>
<main>
<Outlet />
</main>
</div>
),
})
const dashboardIndexRoute = createRoute({
getParentRoute: () => dashboardRoute,
path: '/',
component: () => <div>Dashboard Home</div>,
})
Make sure to include <Outlet /> in parent routes to render child route content.