Доки по разработке
This project is maintained by teniryte
TanStack Router хранит состояние маршрутизации в собственном сторе (тонкая обертка над Zustand). Его можно читать и подписываться на изменения для построения UI вне компонента маршрута, для тулбаров, заголовков страниц или интеграции с аналитикой.
const status = useRouterState({ select: (s) => s.status })
location — { pathname, search, hash, state }. Часто удобно мемоизировать только pathname, чтобы не перерендеривать layout при изменении query.resolvedLocation — фактический URL после всех нормализаций (удаляйте лишние / именно тут).matches — массив активных маршрутов с данными loaders, meta и context.status — idle | loading | submitting. Можно использовать как флажок для глобальных спиннеров.isTransitioning / isLoading — булевы derived-поля, пригодны для disable-кнопок.pendingLocation — информация о следующем переходе (например, для оптимистичных UI).fetchCount и pendingMatches — показывают, сколько loaders сейчас в полёте.Дополнительно доступен router.state.matchesById, что полезно для быстрых lookup-ов без перебора массива.
const router = useRouter()
const currentUserId = router.state?.matchesById['/users/$userId']?.params.userId
Контекст задается при создании:
const router = createRouter({
routeTree,
context: {
auth,
queryClient,
},
})
context доступен в loader, beforeLoad, action, component через Route.useContext():
const { auth } = Route.useContext()
interface RouterContext {
auth: ReturnType<typeof createAuthClient>
queryClient: QueryClient
featureFlags: FeatureFlagService
}
export const router = createRouter({
routeTree,
context: (() => {
const auth = createAuthClient()
const queryClient = new QueryClient()
return { auth, queryClient, featureFlags: createFlags(auth) }
})(),
}) satisfies RootRouteOptions<RouterContext>
Используйте satisfies для безопасной проверки структуры. В тестах можно подменять контекст через router.update({
context: { ...router.options.context, auth: mockAuth },
}).
useRouterState({ select, structuralSharing, equalityFn }) мемоизирует результат и предотвращает перерендер. Возвращайте “плоские” примитивы или мемоизированные объекты.const { pathname, status } = useRouterState({
select: ({ location, status }) => ({ pathname: location.pathname, status }),
equalityFn: (a, b) => a.pathname === b.pathname && a.status === b.status,
})
router.subscribe((state) => ...) — низкоуровневая подписка (очищайте вручную). Удобно для service-workers, логгеров или redux-адAPTERов.router.store — объект Zustand-like, можно использовать для интеграции с DevTools.Лайфхак: если нужно реактивно управлять заголовком страницы, можно сделать кастомный хук:
export function useDocumentTitle() {
const meta = useRouterState({
select: (state) => state.matches.at(-1)?.context?.title ?? state.location.pathname,
})
useEffect(() => {
document.title = meta
}, [meta])
}
router.events.on({
onBeforeLoad: (event) => log(event),
onLoad: (event) => analytics.track('route_loaded', event),
onResolved: () => console.log('done'),
})
События помогают для трекинга, метрик и спиннеров на уровне приложения.
Практические сценарии:
onBeforeNavigate — валидация/отмена перехода (например, несохранённые формы).onLoad + performance.mark — измерение TTI по маршрутам.onResolved + router.store.setState — единый индикатор загрузки.Не забудьте отписаться:
const unsub = router.events.subscribe('onBeforeLoad', handler)
return () => unsub()
| Метод | Назначение |
| — | — |
| router.invalidate() | Глобальный refetch всех loaders. |
| router.invalidateRoute(opts) | Точечная инвалидация. |
| router.load() | Префетч дерева маршрутов (используется в SSR). |
| router.dehydrate() / router.hydrate() | Работа с SSR state. |
| router.reset() | Очистка стора (редко нужно). |
| router.update(opts) | Горячая подмена опций (контекст, routeTree) без размонтирования. |
| router.navigate({ to, search, replace, resetScroll }) | Гибкая навигация с точным контролем поведения скролла. |
| router.buildNext({ to, params }) | Просто получить URL без перехода (для ссылок/OG-тегов). |
const href = router.buildNext({
to: '/projects/$projectId',
params: { projectId },
search: (old) => ({ ...old, filter }),
})
context.Route.useContext() вместо глобальных синглтонов.beforeLoad можно синхронно читать глобальное состояние (redux store).const useThemeStore = create((set) => ({ theme: 'light', toggle: () => set(...) }))
router.events.on({
onResolved: ({ toLocation }) => {
if (toLocation.pathname.startsWith('/settings')) {
useThemeStore.getState().toggle()
}
},
})
router.subscribe((state) => {
reduxStore.dispatch(routerStateUpdated(state))
})
Не тащите UI-стор внутрь context: вместо этого пробрасывайте сервисы/клиенты, а сами UI-состояния держите в своих сторах.
router.options.debug = true.RouterDevtools, вкладка Router State.useRouterState({ select: (state) => state.location }) внутри custom hooks для логирования изменений.window.router = router в dev-режиме позволяет вручную вызывать router.navigate из консоли.persist: true в router.store при отладке оффлайн-сценариев.router.store.subscribe(console.table) — быстрый способ увидеть эволюцию состояния (не забудьте удалить).if (import.meta.env.DEV) {
router.store.subscribe((state) => {
console.debug('[router]', state.location.pathname, state.status)
})
}
location.state.modal = 'editUser' и рендерьте модалку вне маршрутов, подписавшись на useRouterState.onBeforeLoad сохраняйте event.next.location в analytics, чтобы UI сразу показывал skeleton нового маршрута.router.events.onNavigate и прокидывайте пути в мобильную оболочку.onRouteMatch запрашивайте подсказки через router.load({ preload: true }), чтобы на hover линка данные уже были в кэше.useRouterState/useContext, а не глобальные импорты.dehydrate/hydrate.equalityFn или кастомные мемоизации.