Доки по разработке
This project is maintained by teniryte
В TanStack Router v1 маршруты описываются как дерево. Каждый узел определяет:
path) или индикатор индекса (index),getParentRoute),loader, beforeLoad, pendingComponent),context) и метаданные (staticData, meta).Такой подход даёт:
session, queryClient, theme и т. д.).linkBuilder с автодополнением и подсказками IDE.Лайфхак: если видите странные ошибки типов в ссылках, просто переимпортируйте
routeTree— генератор прогоняет весь типовой вывод и подсвечивает конфликты путей ещё до runtime.
// src/routes/routeTree.gen.ts (генерируется плагином)
import { Route as rootRoute } from './__root'
import { Route as indexRoute } from './index'
import { Route as blogRoute } from './blog'
import { Route as blogPostRoute } from './blog.$postId'
export const routeTree = rootRoute.addChildren([
indexRoute,
blogRoute.addChildren([blogPostRoute]),
])
routeTree руками.context.// routes/blog.tsx
import { Outlet, createRoute } from '@tanstack/react-router'
import { Route as rootRoute } from './__root'
export const Route = createRoute({
getParentRoute: () => rootRoute,
path: 'blog',
beforeLoad: ({ context }) => {
if (!context.session) {
throw new Redirect({ to: '/login', search: { next: '/blog' } })
}
return { theme: context.theme ?? 'light' }
},
component: () => (
<BlogLayout>
<Suspense fallback={<BlogSkeleton />}>
<Outlet />
</Suspense>
</BlogLayout>
),
})
Лайфхаки:
beforeLoad исполняется один раз на заход и кэширует результат, пока родитель не сброшен — идеально для проверки прав.lazy, если импортировать через createLazyRoute.Route.useRouteContext({ from: blogRoute.id }), не пробрасывая пропсы.export const Route = createRoute({
getParentRoute: () => blogRoute,
path: '/',
component: () => <PostsList />,
})
Индексы не имеют собственного сегмента URL, поэтому:
addIndexRoute() для краткости — у него нет path, и ссылки к нему не требуют params./settings → вкладка «Profile»).'': индекс срабатывает только когда родитель полностью совпал без дополнительных сегментов.export const Route = createRoute({
getParentRoute: () => blogRoute,
path: '$postId',
parseParams: (raw) => ({
postId: Number(raw.postId),
}),
stringifyParams: ({ postId }) => ({
postId: postId.toString(),
}),
component: PostView,
})
Советы:
parseParams (числа, UUID, даты). Это избавляет от Number(searchParams.get(...)) по всему коду.zod или valibot, чтобы возвращать безопасные структуры.'$postId?'. В parseParams возвращайте дефолт, чтобы не ловить undefined в компонентах.path: '$category/$slug'.validateSearch наследуется вниз — задайте базовую схему на layout и расширяйте в дочках.import { z } from 'zod'
export const Route = createRoute({
getParentRoute: () => blogRoute,
path: '$category/$slug',
validateSearch: z.object({
draft: z.boolean().default(false),
cursor: z.string().optional(),
}),
loader: async ({ params, context }) => {
const queryKey = ['post', params.category, params.slug]
await context.queryClient.ensureQueryData({
queryKey,
queryFn: () => fetchPost(queryKey),
})
return { queryKey }
},
})
Лайфхак: используйте router.navigate({ search: prev => ({ ...prev, cursor: nextCursor }) }) — тогда типы search и мемоизация гарантированы.
@tanstack/router-plugin собирает дерево из файлов по шаблону routes/**/*.tsx.
Правила:
Route.blog.$postId.lazy.tsx → blog/$postId).__root.tsx обязателен (глобальные провайдеры, error boundaries).Полезные мелочи:
.lazy.tsx автоматически помечаются как lazyRouteComponent.routeContext + loader прямо в файле, плагин подхватит экспорт.watch: true, чтобы при добавлении файла routeTree.gen.ts пересобирался мгновенно (и коммитьте этот файл, иначе типы в CI «поедут»).Outlet./app, /marketing, /admin). Так проще выключать крупные ветки флагами.beforeLoad + context.session). Не дублируйте проверки в каждом листе.routeTree.buildRouteTree() и экспортируйте routes для ссылок вне React:
export const routes = router.routeTree.buildRouteTree()
routes.blog.post.$postId({ postId: 42 }) // → /blog/42
:team.myapp.com), объявите корень path: '$teamId' и используйте router.basepath.export const Route = createRoute({
getParentRoute: () => blogRoute,
path: '$postId',
beforeLoad: async ({ context, params }) => {
const post = await context.api.getPost(params.postId)
return { post }
},
component: () => {
const { post } = Route.useRouteContext()
return <PostView post={post} />
},
})
beforeLoad выполняется до loader и может кинуть Redirect/Error, если данных нет.Route.useRouteContext({ from: parentRoute.id }).export const Route = createRoute({
getParentRoute: () => blogRoute,
path: '$postId',
loader: ({ context, params }) =>
context.queryClient.ensureQueryData({
queryKey: ['post', params.postId],
queryFn: () => fetchPost(params.postId),
}),
component: () => {
const { params } = Route.useRoute()
const { data } = useQuery(['post', params.postId], { enabled: false })
return <PostView post={data} />
},
})
ensureQueryData работает одинаково на сервере и клиенте, поэтому гидратация прозрачна.onBeforeUnload можно отменять долгие запросы через router.store.router.preloadRoute на onPointerEnter ссылок.*.lazy.tsx и экспортируйте Route = createLazyRoute(...).loader можно импортировать код await import('./chunks/post') — роутер автоматически покажет pendingComponent.pendingComponent и pendingMs, чтобы избегать дерганий UI.router.navigate({
to: blogPostRoute.to,
params: { postId: 1 },
search: { highlight: 'comments' },
mask: { to: '/blog', search: true }, // user видит маску
replace: false,
})
createFileRoute и createLazyFileRoute генерируют to и fullPath, которые гарантированно совпадают с деревом.useLinkProps даёт готовые href/onClick, плюс опции preload, preloadDelay, pending.mask помогает прятать настоящие query-параметры (например, ?cursor=) за более чистым URL.routerDevtools() — вкладка Route Tree показывает pending/loader/error по каждому узлу.Invalidations.Prefetch помогает понять, какие ссылки триггерят router.preloadRoute.assertRouteTree.validateSearch они приезжают как any и ломают типы ссылок. Создайте базовую схему хотя бы для locale.defer, убедитесь, что компонент обёрнут в Suspense.zustand/redux в context, прокидывайте провайдером на уровне __root.getParentRoute.Outlet и/или useRouteContext.parse/stringify.addIndexRoute).validateSearch покрывает критичные query-параметры.preload/ensureQueryData).routeTree.gen.ts синхронизирован с файловой системой.examples/react/file-based-routing, examples/react/start-client-router-ssr