Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tanstack/router/llms.txt
Use this file to discover all available pages before exploring further.
TanStack Router provides powerful data loading capabilities through loaders, allowing you to fetch data before rendering routes.
Overview
Route loaders run before a route renders, ensuring data is available when components mount. Key features:
- Parallel loading - Multiple loaders run concurrently
- Type-safe - Full TypeScript support for loaded data
- Caching - Built-in caching with configurable strategies
- Preloading - Prefetch data before navigation
- Streaming - Support for deferred data loading
Basic loader
Define a loader using the loader option:
import { createFileRoute } from '@tanstack/react-router'
import { fetchPost } from '@/api'
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 <h1>{post.title}</h1>
}
Loader context
Loaders receive a context object with useful properties:
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params, context, deps, abortController, preload }) => {
// params - Path and search params
const { postId } = params
// context - Route context from parent routes
const { queryClient } = context
// deps - Explicit dependencies for cache invalidation
// abortController - For cancelling requests
const signal = abortController.signal
// preload - Boolean indicating if this is a preload
if (preload) {
// Handle preload differently if needed
}
return await fetchPost(postId, { signal })
},
})
beforeLoad hook
Run code before the loader, useful for authentication or redirects:
import { redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
beforeLoad: async ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({
to: '/login',
search: {
redirect: '/dashboard',
},
})
}
// Return additional context for the loader
return {
user: context.auth.user,
}
},
loader: async ({ context }) => {
// context.user is now available from beforeLoad
return await fetchDashboardData(context.user.id)
},
})
Parallel vs sequential loading
Parallel loading (default)
Loaders run concurrently:
const layoutRoute = createRoute({
path: '/dashboard',
loader: async () => {
const user = await fetchUser() // Runs in parallel with child
return { user }
},
})
const dashboardRoute = createRoute({
getParentRoute: () => layoutRoute,
path: '/',
loader: async () => {
const stats = await fetchStats() // Runs in parallel with parent
return { stats }
},
})
Sequential loading
Access parent data in child loaders:
const userRoute = createRoute({
path: '/users/$userId',
loader: async ({ params }) => {
const user = await fetchUser(params.userId)
return { user }
},
})
const userPostsRoute = createRoute({
getParentRoute: () => userRoute,
path: '/posts',
loader: async ({ context }) => {
// Wait for parent loader to complete
await context.loaderPromise
const parentData = context.loaderData
// Now fetch posts for this specific user
const posts = await fetchUserPosts(parentData.user.id)
return { posts }
},
})
Caching
Control how long loader data is cached:
export const Route = createFileRoute('/posts')({
loader: async () => {
return await fetchPosts()
},
// Cache for 10 seconds
staleTime: 10_000,
// Keep in cache for 5 minutes even when route is inactive
gcTime: 5 * 60 * 1000,
})
Preloading
Prefetch data before navigation:
import { Link } from '@tanstack/react-router'
function PostsList() {
return (
<div>
{posts.map((post) => (
<Link
key={post.id}
to="/posts/$postId"
params={{ postId: post.id }}
preload="intent" // Preload on hover/focus
>
{post.title}
</Link>
))}
</div>
)
}
Preload modes:
false - No preloading
"intent" - Preload on hover or focus
"viewport" - Preload when link enters viewport
"render" - Preload immediately when link renders
Deferred data
Stream data that takes longer to load:
import { defer } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
loader: async () => {
// Fast data loads immediately
const user = await fetchUser()
// Slow data is deferred
const slowData = defer(fetchSlowData())
return { user, slowData }
},
component: DashboardComponent,
})
function DashboardComponent() {
const { user, slowData } = Route.useLoaderData()
return (
<div>
<h1>Welcome, {user.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<Await promise={slowData}>
{(data) => <SlowComponent data={data} />}
</Await>
</Suspense>
</div>
)
}
Error handling
Handle loader errors with error boundaries:
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
if (!post) {
throw new Error('Post not found')
}
return { post }
},
errorComponent: ({ error, reset }) => (
<div>
<h2>Error loading post</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
),
})
Invalidation
Invalidate cached loader data:
import { useRouter } from '@tanstack/react-router'
function UpdatePostButton({ postId }: { postId: string }) {
const router = useRouter()
const handleUpdate = async () => {
await updatePost(postId)
// Invalidate specific route
router.invalidate({
to: '/posts/$postId',
params: { postId },
})
// Or invalidate all routes
router.invalidate()
}
return <button onClick={handleUpdate}>Update</button>
}
Loader dependencies
Specify explicit dependencies for cache invalidation:
export const Route = createFileRoute('/posts')({
loaderDeps: ({ search }) => ({
page: search.page,
filter: search.filter,
}),
loader: async ({ deps }) => {
// Loader re-runs when page or filter changes
return await fetchPosts(deps.page, deps.filter)
},
})
Best practices
Loaders should only fetch data. Move business logic to separate functions.
Always pass the abort signal to fetch calls for proper cancellation.
Set appropriate staleTime and gcTime to reduce unnecessary requests.
Provide helpful error messages and recovery options.
Next steps
Error handling
Learn error boundary patterns
Loaders concept
Deep dive into loader architecture