Dev Highlights

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

This project is maintained by teniryte

Динамические параметры пути

export const Route = createRoute({
  getParentRoute: () => rootRoute,
  path: 'projects/$projectId/tasks/$taskId',
  parseParams: ({ projectId, taskId }) => ({
    projectId: Number(projectId),
    taskId: taskId as TaskId,
  }),
  stringifyParams: ({ projectId, taskId }) => ({
    projectId: projectId.toString(),
    taskId,
  }),
})

Множественные сегменты

Тонкости и лайфхаки

Search-параметры

const Route = createRoute({
  getParentRoute: () => rootRoute,
  path: 'reports',
  validateSearch: z.object({
    tab: z.enum(['summary', 'details']).default('summary'),
    from: z.coerce.date().optional(),
    to: z.coerce.date().optional(),
  }),
})

Использование в компонентах

const { tab, from } = Route.useSearch()
const params = Route.useParams()

useSearch возвращает типизированный объект; дефолты прописываются в схеме и работают и при SSR, и при навигации из кода.

Двусторонняя синхронизация

const navigate = Route.useNavigate()
const search = Route.useSearch()

const toggleTab = () =>
  navigate({
    search: (prev) => ({ ...prev, tab: prev.tab === 'summary' ? 'details' : 'summary' }),
    replace: true,
  })

Глобальные схемы поиска

Кастомный парсер/сериализатор

const Route = createRoute({
  getParentRoute: () => rootRoute,
  path: 'grid',
  parseSearch: (search) => ({
    page: Number(search.page ?? 1),
    tags: (search.tags?.split(',') ?? []).filter(Boolean),
  }),
  stringifySearch: ({ page, tags }) => ({
    page,
    ...(tags.length ? { tags: tags.join(',') } : {}),
  }),
})

Работа с URL вне компонентов

const url = router.buildLocation({
  to: '/blog/$postId',
  params: { postId: 7 },
  search: { highlight: true },
  hash: 'comments',
})
// → { href: '/blog/7?highlight=true#comments', pathname: '/blog/7', searchStr: '?highlight=true', ... }

Типизированные ссылки

Используйте router.buildRouteTree() или router.routes (при автоматической генерации) для создания helpers:

routes.blog.post.$postId({ postId: 8, search: { preview: true } })

Hash и состояние

Лучшие практики валидации

  1. Применяйте zod/valibot или кастомную функцию.
  2. Возвращайте значения в нужных типах (Date, number), чтобы компоненты были максимально «тупыми».
  3. Всегда задавайте дефолты — так useSearch() никогда не вернёт undefined.
  4. Используйте shouldReload/loaderDeps, чтобы чётко описать, какие параметры должны перезагружать данные.
  5. Логируйте ошибки валидации (Sentry breadcrumb, analytics), чтобы понимать, какие URL ввёл пользователь.
  6. Общие схемы держите в shared-пакете и переиспользуйте в SSR/API — меньше расхождений между клиентом и сервером.

Переиспользуемые схемы и композиция

const paginationSearch = z.object({
  page: z.coerce.number().int().positive().default(1),
  pageSize: z.coerce.number().int().positive().max(200).default(25),
})

const filtersSearch = paginationSearch.extend({
  statuses: z.array(z.enum(['open', 'closed', 'draft'])).default(['open']),
})

export const TicketsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: 'tickets',
  validateSearch: filtersSearch,
})

Пример: таблица с фильтрами и колонками

const navigate = Route.useNavigate()
const search = Route.useSearch()

const toggleColumn = (id: string) =>
  navigate({
    search: (prev) => ({
      ...prev,
      columns: { ...prev.columns, [id]: !prev.columns?.[id] },
    }),
    replace: true,
  })

Отладка и диагностика

Чек-лист