Use when implementing, REVIEWING, or ARCHITECTING TanStack Router - type-safe file-based routing, route loaders, search params validation, navigation patterns, auth guards, migration from React Router
Authoritative patterns for @tanstack/react-router with type-safe routing, loaders, and Query integration.
Use this skill when:
useNavigate and LinkensureQueryDataNOT for: React Router v6 (different API), TanStack Query alone (see ), or Next.js routing.
using-tanstack-query| Pattern | What It Does | Reference |
|---|---|---|
| File-based routing | $param.tsx, _layout.tsx, (groups), index.tsx | file-conventions.md |
| Route loaders | Prefetch data before render with loader() | loader-patterns.md |
| beforeLoad vs loader | Sequential auth checks vs parallel data loading | loader-patterns.md |
| Search params | Type-safe query strings with Zod | search-validation.md |
| Navigation | Type-safe useNavigate() and <Link> | router-navigation-patterns.md |
| Auth guards | Redirect unauthenticated users with beforeLoad() | router-routing-guide.md |
| Code splitting | Lazy load routes with .lazy.tsx files | code-splitting.md |
| Error handling | notFoundComponent, errorComponent, pendingComponent | error-handling.md |
| Query integration | Prime TanStack Query cache in loaders | integration-patterns.md |
| Route context | Share services across routes | router-routing-guide.md |
src/routes/
├── __root.tsx # Root layout (required)
├── index.tsx # / route
├── assets/
│ ├── index.tsx # /assets
│ └── $assetId.tsx # /assets/:assetId (dynamic param)
└── settings/
├── _layout.tsx # Pathless layout
├── index.tsx # /settings
└── profile.tsx # /settings/profile
For complete file naming conventions, see file-conventions.md.
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/assets')({
component: AssetList,
})
function AssetList() {
return <div>Asset List</div>
}
export const Route = createFileRoute('/assets/$assetId')({
loader: async ({ params }) => {
const asset = await fetchAsset(params.assetId)
return { asset }
},
component: AssetDetails,
})
function AssetDetails() {
const { asset } = Route.useLoaderData()
return <div>{asset.name}</div>
}
For loader patterns (beforeLoad vs loader, loaderDeps, parallel loading), see loader-patterns.md.
import { zodValidator, fallback } from '@tanstack/zod-adapter'
import { z } from 'zod'
const searchSchema = z.object({
status: z.enum(['active', 'inactive']).optional(),
page: fallback(z.number().int().positive(), 1),
search: z.string().default(''),
})
export const Route = createFileRoute('/assets')({
validateSearch: zodValidator(searchSchema),
})
function AssetList() {
const { status, page, search } = Route.useSearch()
return <div>Page {page}</div>
}
For detailed validation patterns, see search-validation.md.
import { useNavigate, Link } from '@tanstack/react-router'
function AssetCard({ asset }: { asset: Asset }) {
const navigate = useNavigate()
return (
<Link
to="/assets/$assetId"
params={{ assetId: asset.id }}
search={{ tab: 'risks' }}
>
{asset.name}
</Link>
)
}
For navigation patterns, see router-navigation-patterns.md.
import { createFileRoute, redirect } from "@tanstack/react-router";
export const Route = createFileRoute("/settings")({
beforeLoad: async ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: "/login", search: { redirect: "/settings" } });
}
},
});
// assets.tsx - Critical options
export const Route = createFileRoute("/assets")({
loader: async () => fetchAssets(),
});
// assets.lazy.tsx - Non-critical options
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/assets")({
component: AssetList,
pendingComponent: LoadingSpinner,
});
For code splitting patterns, see code-splitting.md.
import { queryOptions } from "@tanstack/react-query";
const assetQueryOptions = (id: string) =>
queryOptions({
queryKey: ["assets", id],
queryFn: () => fetchAsset(id),
});
export const Route = createFileRoute("/assets/$assetId")({
loader: async ({ params, context }) => {
await context.queryClient.ensureQueryData(assetQueryOptions(params.assetId));
},
});
For Query integration patterns, see integration-patterns.md.
❌ Don't use string routes:
navigate("/assets/" + assetId); // No type safety
✅ Use type-safe params:
navigate({ to: "/assets/$assetId", params: { assetId } });
❌ Don't fetch in components:
const { data } = useQuery(["assets", assetId], fetchAsset); // Loading flicker
✅ Fetch in loaders: