Dev Highlights

Доки по разработке

This project is maintained by teniryte

Общий поток SSR

  1. На сервере создайте router и вызовите await router.load(), передав URL из запроса.
  2. Выполните router.dehydrate() и передайте результат в HTML.
  3. На клиенте создайте router с теми же настройками и вызовите router.hydrate(dehydratedState).
  4. Отрендерьте <RouterProvider router={router} />.

Дополнительные нюансы

// 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} />)

SSR + TanStack Query

Частый паттерн — прогонять 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.

Инкрементальная гидратация

Prefetch стратегий

Умные подсказки

Prefetch из React Query

Если вы используете 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, и для клиентского префетча.

Префетч по событиям

Работа офлайн

Пошаговая стратегия

  1. В beforeLoad сначала читайте кэш (IndexedDB/Cache Storage). Если данные найдены и свежие — возвращайте их через throw redirect на defer (или напрямую из loader).
  2. Параллельно запускайте сетевой запрос. Если он завершается успешным ответом — обновляйте кэш и вызывайте router.invalidate() для маршрута.
  3. В офлайн-баннере подписывайтесь на window.addEventListener('online', ...) и инициируйте router.invalidate() или router.load(), чтобы догрузить свежие данные.
  4. Для критичных маршрутов держите статический fallback-компонент в errorComponent, чтобы сразу показывать офлайн-состояние.

Интеграция с Service Worker

Работа с background sync

Если маршрут триггерит мутацию, регистрируйте navigator.serviceWorker.ready.then(sw => sw.sync.register('sync-post')), а в onSubmit возвращайте optimistic UI. Когда sync завершается, вызывайте router.invalidate() или queryClient.invalidateQueries.

Streaming / Suspense SSR

const invoiceRoute = createRoute({
  getParentRoute: () => invoicesRoute,
  path: '$invoiceId',
  loader: async () => ({
    invoice: defer(fetchInvoice()),
    audit: fetchAuditSync(),
  }),
  component: () => (
    <Suspense fallback={<Skeleton />}>
      <InvoiceDetails />
    </Suspense>
  ),
})

Handling Not Found и Redirect

Советы

Конфигурация history

Чек-лист