--- description: Vite + TanStack Router v1 + TanStack Query v5 — routing, loaders, queries, mutations globs: apps/web/**/*.{tsx,ts} alwaysApply: false --- # Vite + TanStack Router + Query Фронтенд: **Vite SPA**, не Next.js. Нет Server Components, App Router, `'use client'`. ## Структура ``` apps/web/src/ routes/ # file-based routes (__root.tsx, _auth/, ...) queries/ # queryOptions factories + key factories lib/ # api-client, queryClient, auth, schemas components/ # domain + layout (UI primitives → @cdnmanager/ui) main.tsx ``` ## Архитектура - **Router** — маршрутизация, URL state, navigation, loaders - **Query** — server state, cache, mutations - **Loader** — `queryClient.ensureQueryData()` до рендера → без спиннеров на route data - **Компоненты** — UI; данные из Query cache ## QueryClient + Router ```ts // lib/queryClient.ts export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 } }, }) // lib/router.ts export const router = createRouter({ routeTree, context: { queryClient }, defaultPreload: 'intent', }) declare module '@tanstack/react-router' { interface Register { router: typeof router } } ``` ## Query definitions - `queryOptions` factories в `queries/`, не inline в компонентах - Key factories: `all` → `lists` / `details` → `list(filters)` / `detail(id)` ```ts export const serviceKeys = { all: ['services'] as const, list: () => [...serviceKeys.all, 'list'] as const, } export const servicesQueryOptions = () => queryOptions({ queryKey: serviceKeys.list(), queryFn: () => api.get('/api/v1/services'), }) ``` ## Loader + component ```tsx export const Route = createFileRoute('/_auth/services')({ loader: ({ context: { queryClient } }) => queryClient.ensureQueryData(servicesQueryOptions()), component: ServicesPage, }) function ServicesPage() { const { data } = useQuery(servicesQueryOptions()) // из cache loader return ... } ``` ## Search params - Zod + `validateSearch`; доступ через `Route.useSearch()` - Search params = source of truth для фильтров/пагинации - Передавать в `queryOptions` для query key и fetcher ## Mutations ```ts onSuccess: (newItem) => { queryClient.setQueryData(keys.detail(newItem.id), newItem) queryClient.invalidateQueries({ queryKey: keys.lists() }) } ``` - `setQueryData` + `invalidateQueries`, не только invalidate - Навигация после create — когда cache уже тёплый ## Routing - `createFileRoute` для file-based routes - `` для внутренней навигации, не `` - Pathless layouts: `_auth/` для protected routes - Auth guard в `beforeLoad` pathless route ## Запреты - `useEffect` для fetch данных — только loader / `useQuery` - Inline `queryKey` в компонентах — только factories из `queries/` - `useQuery` с позиционными аргументами (v5 — только options object) - `window.location` для search params ## Prefetch `onMouseEnter` на `` → `queryClient.prefetchQuery(detailOptions(id))` ## DevTools Только в dev: `TanStackRouterDevtools`, `ReactQueryDevtools`