Доки по разработке
This project is maintained by teniryte
router и вызовите await router.load(), передав URL из запроса.router.dehydrate() и передайте результат в HTML.router.hydrate(dehydratedState).<RouterProvider router={router} />.routeTree, но создавайте новый router и context на каждый HTTP-запрос.initialState не только location, но и matches, если хотите сократить повторную работу router.load на клиенте.router.options.context, формируйте серверный контекст (cookies, user, feature flags) синхронно до router.load, чтобы матчи могли сразу читать его в beforeLoad.hydrateRoot в startTransition, чтобы Suspense-переходы были плавнее.router.serialize() + router.deserialize() вместо прямой передачи dehydrated (например, при рендеринге в edge-средах).// server.ts
const router = createRouter({ routeTree, context })
await router.load({
initialState: {
location: createMemoryHistoryLocation(request.url),
},
})
const dehydrated = router.dehydrate()
const html = renderToString(
<RouterProvider router={router} />
)
// client.tsx
const router = createRouter({ routeTree, context })
router.hydrate(window.__ROUTER_STATE__)
hydrateRoot(document, <RouterProvider router={router} />)
Частый паттерн — прогонять loaders и React Query вместе, чтобы не дублировать fetch-логику.
// server.ts
const queryClient = new QueryClient()
const router = createRouter({
routeTree,
context: { queryClient },
})
await router.load({
preload: true,
initialState: {
location: createMemoryHistoryLocation(request.url),
},
})
const dehydratedRouter = router.dehydrate()
const dehydratedQuery = dehydrate(queryClient)
return renderToString(
<RouterProvider router={router} context={{ dehydratedQuery }} />
)
На клиенте: сначала hydrate(dehydratedRouter), затем queryClient.hydrate(dehydratedQuery) и только потом hydrateRoot.
router.load({ preload: true }) на сервере, чтобы подготовить дочерние маршруты до первого байта.router.dehydrateData({ includeRouteTree: false }), а остальной UI догружайте через router.loadRoute после hydrateRoot.lazyRouteComponent + route.options.staleTime, чтобы не перезапрашивать данные при последующей клиентской навигации.router.preloadRoute — ручное предзагружение.preload="viewport" — загрузка при попадании ссылки в viewport.beforeLoad + router.load() позволяют подготавливать данные перед переходом (например, в background task).Link принимает preload, preloadDelay, preloadMaxAge, preloadGcMaxAge — комбинируйте их, чтобы не перегружать сеть.router.preloadRoute можно вызывать из useEffect, подписываясь на события скролла и предугадывая переходы.lazy(() => import('./route')) в createRoute. Префетч автоматически вытянет и код, и данные.router.buildLocation({ to: '/dashboard' }) и передавайте его в router.load({ search, params }), чтобы прогревать различные варианты query-параметров.router.cancelPreload({ to }) (v1.10+) или храните AbortController в beforeLoad.Если вы используете TanStack Query: вызывайте queryClient.prefetchQuery внутри loader, затем queryClient.dehydrate.
const postsRoute = createFileRoute('/posts')({
beforeLoad: async ({ context }) => {
await context.queryClient.prefetchQuery({
queryKey: ['posts', 'list'],
queryFn: () => fetchPosts(),
staleTime: 60_000,
})
},
})
Так как beforeLoad выполняется и на сервере, и на клиенте, этот код автоматически работает и для SSR, и для клиентского префетча.
router.options.linkPreloadScrollDelay — глобальная настройка, чтобы префетч начинался спустя N мс после попадания ссылки в viewport.router.subscribe('onResolved', cb) и замеряйте, сколько времени занял preload; результаты можно сохранять в метриках и динамически регулировать агрессивность префетча.postMessage, чтобы воркер скачал API и статику параллельно.router.options.defaultPreloadStaleTime можно увеличить, чтобы данные считались актуальными дольше.navigator.onLine и router.invalidate() при восстановлении соединения.beforeLoad сначала читайте кэш (IndexedDB/Cache Storage). Если данные найдены и свежие — возвращайте их через throw redirect на defer (или напрямую из loader).router.invalidate() для маршрута.window.addEventListener('online', ...) и инициируйте router.invalidate() или router.load(), чтобы догрузить свежие данные.errorComponent, чтобы сразу показывать офлайн-состояние./router-data/* и храните ответы в Cache Storage.router.serialize() и сохраняйте результат в IndexedDB после каждого успешного load, чтобы клиент мог стартовать с последнего известного состояния без сети.clients.matchAll и шлите сообщение router:refresh, которое на клиенте вызывает router.invalidate().Если маршрут триггерит мутацию, регистрируйте navigator.serviceWorker.ready.then(sw => sw.sync.register('sync-post')), а в onSubmit возвращайте optimistic UI. Когда sync завершается, вызывайте router.invalidate() или queryClient.invalidateQueries.
defer из loaders и используйте renderToPipeableStream (React 18).const invoiceRoute = createRoute({
getParentRoute: () => invoicesRoute,
path: '$invoiceId',
loader: async () => ({
invoice: defer(fetchInvoice()),
audit: fetchAuditSync(),
}),
component: () => (
<Suspense fallback={<Skeleton />}>
<InvoiceDetails />
</Suspense>
),
})
router.dehydrateData({ includePending: true }), чтобы клиент продолжил ожидать те же defer-промисы.renderToPipeableStream используйте onShellReady для отправки HTML сразу после готовности критического маршрута и onAllReady для закрытия стрима.redirect и отвечайте статусом 302/307.router.notFound() можно интегрировать с ответом 404.throw redirect({ to: '/login', statusCode: 302 }) работает как на сервере, так и на клиенте, поэтому используйте единую функцию requireAuth.router.load в try/catch, чтобы поймать RouterError. Полезно логировать error.routerState.matches для быстрого дебага.throw notFound({ resetScroll: false }), чтобы не сбрасывать позицию, если пользователь уже был на странице.router.options.defaultNotFoundComponent, чтобы не дублировать 404-разметку во всех ветках дерева.createMemoryHistory), который TanStack Router создает автоматически при router.load({ initialState }).createBrowserHistory.router.hydrate({ initialMatches }) если на сервере переносите matches. Это особенно полезно при edge-rendering, где request.url может отличаться из-за префиксов.createHashHistory) на клиенте не забудьте передать history: createHashHistory() в createRouter, иначе гидратация сломается.createMemoryHistory({ initialEntries: [request.url] }), если нужно симулировать push/pop до рендера.router.load() перед рендером.router.dehydrate() и queryClient.dehydrate() синхронно сохраняются и восстанавливаются.