Dev Highlights

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

This project is maintained by teniryte

Конструкторы маршрутов

Лайфхаки объявления маршрутов

Основные опции route

| Опция | Назначение | | — | — | | path | Статический ('settings') или динамический ('$userId') сегмент. Разрешены вложенные сегменты ('$orgId/projects'). | | id | Пользовательский идентификатор, обязателен, если нет path. Удобен для layout-узлов. | | component / pendingComponent / errorComponent | Компоненты для состояний. Можно задавать функцией или ссылкой на компонент. | | loader | Загрузка данных; поддерживает Promise, defer, работу с Query Client. | | beforeLoad | Выполняется до монтирования; подходит для guard-логики и вычисления context. | | shouldReload | Управляет повторными вызовами loader-а; можно возвращать false, если данные кешируются Query Client. | | meta | Свободная полезная нагрузка (title, breadcrumbs, featureFlags). | | validateSearch / parseParams | Типобезопасная валидация search/params через zod/valibot. | | pendingMs | Таймаут до показа pendingComponent; снижает “мигание” спиннера. | | wrapInSuspense | Принудительно оборачивает вложенные компоненты в Suspense (полезно при createLazyRoute). |

Работа с path/id

Пример layout-дерева

// routes/dashboard.tsx
export const Route = createRoute({
  getParentRoute: () => rootRoute,
  id: 'dashboard',
  component: () => (
    <DashboardShell>
      <Outlet />
    </DashboardShell>
  ),
  meta: () => ({ title: 'Панель' }),
  beforeLoad: ({ context }) => {
    if (!context.auth.user) {
      throw redirect({ to: '/login', mask: { to: '/' } })
    }
  },
})
// routes/dashboard.analytics.tsx
export const Route = createRoute({
  getParentRoute: () => dashboardRoute,
  path: 'analytics',
  component: AnalyticsPage,
  loader: async ({ context, search }) =>
    context.api.analytics.get({ range: search.range ?? '7d' }),
  validateSearch: z.object({
    range: z.enum(['7d', '30d']).default('7d'),
  }),
})

Error & pending boundary на уровне layout

createRoute({
  getParentRoute: () => dashboardRoute,
  path: 'reports',
  pendingMs: 150,
  pendingComponent: () => <Spinner />,
  errorComponent: ({ error }) => <ErrorPanel error={error} />,
  loader: async ({ context }) => context.api.reports.list(),
  component: ReportsPage,
})

Смешанный пример с ленивыми файлами

// routes/dashboard.reports.lazy.tsx
export const Route = createLazyRoute({
  component: async () => {
    const { ReportsLayout } = await import('./ReportsLayout')
    return { Component: ReportsLayout }
  },
  pendingComponent: () => <SkeletonReports />,
})

// routes/dashboard.reports.$reportId.tsx
export const Route = createFileRoute('/dashboard/reports/$reportId')({
  loader: async ({ params, context }) =>
    context.queryClient.ensureQueryData(reportQuery(params.reportId)),
  component: ReportDetails,
})

Работа с context

context передается из createRouter({ context }) и доступен во всех loader/beforeLoad.

const router = createRouter({
  routeTree,
  context: {
    auth: authClient,
    queryClient,
    featureFlags,
  },
})

Практические советы по context

Реиспользование настроек через route factories

const authRouteFactory = rootRoute.createRoute({
  beforeLoad: ({ context }) => {
    if (!context.auth.user) throw redirect({ to: '/login' })
  },
})

export const settingsRoute = authRouteFactory.createRoute({
  path: 'settings',
  component: SettingsPage,
})

Фабрики + динамические параметры

const orgRouteFactory = authRouteFactory.createRoute({
  parseParams: (params) => ({
    orgId: z.string().uuid().parse(params.orgId),
  }),
})

export const orgMembersRoute = orgRouteFactory.createRoute({
  path: '$orgId/members',
  loader: ({ params, context }) =>
    context.api.org(params.orgId).members(),
})

Типы компонентов

Компонентные паттерны

Контроль перерендеров

Lazy-маршруты и prefetch

const ReportsLink = () => {
  const router = useRouter()
  return (
    <Link
      to="/dashboard/reports"
      onMouseEnter={() => router.loadRoute('/dashboard/reports')}
    >
      Отчеты
    </Link>
  )
}

Loader best practices

const reportsRoute = dashboardRoute.createRoute({
  path: 'reports',
  loaderDeps: ({ search }) => search.status ?? 'all',
  loader: ({ loaderDeps, context }) =>
    context.api.reports.list({ status: loaderDeps }),
})

Prefetch и переходы

await router.navigate({
  to: '/dashboard/reports/$reportId',
  params: { reportId },
  search: (prev) => ({ ...prev, focus: 'chart' }),
  mask: { to: '/dashboard', search: true },
  replace: true,
})

Отладка layout-дерева

Чек-лист