React
TanStack Router: type-safe роутинг
Классическая сцена с React Router: компонент переходит по ссылке to='/users/42', читает useParams и получает id типа string | undefined, хотя по структуре маршрута он всегда есть. Кто-то переименовал путь с /users на /people, ссылки в десяти местах молча сломались, а заметили это только тестировщики. Search-параметры вообще живут как сырые строки, которые каждый парсит руками. TanStack Router закрывает этот класс ошибок: маршруты, параметры и search полностью типизированы, и неверная ссылка не компилируется.
- Дашборды с глубокой навигацией и состоянием в URL: фильтры, сортировки и пагинация хранятся в типизированных search-параметрах
- Внутренние инструменты и админки, где важно, чтобы рефакторинг маршрута ломал билд, а не прод
- Приложения, где данные грузятся на уровне маршрута через loaders, а компонент получает их уже готовыми
- Команды, переходящие с React Router ради сквозной типобезопасности ссылок и параметров
- Связки TanStack Router плюс TanStack Query, где loader предзагружает данные в общий кэш
Предварительные знания
- Понимание клиентского роутинга: маршруты, параметры пути, навигация без перезагрузки
- Идея файлового роутинга и серверной загрузки из Next.js App Router
- Базовый TypeScript: дженерики и вывод типов на уровне идеи
Почему понадобился ещё один роутер
К началу 2020-х React Router был стандартом, но оставался в основном типонеосведомлённым: параметры пути и search возвращались как строки или необязательные значения, а корректность ссылок проверялась только в рантайме. Таннер Линсли, уже сделавший React Query, перенёс ту же одержимость типобезопасностью на роутинг. TanStack Router (стабильный с конца 2023) строит из дерева маршрутов точный тип всех путей, их параметров и search-схем, так что навигация проверяется компилятором. Параллельно loaders сделали загрузку данных частью маршрута, а не компонента, что убирает водопады запросов.
Маршруты, проверяемые компилятором
В типонеосведомлённом роутере путь это строка, а параметры - то, что удалось вытащить в рантайме. Ссылка to='/uesrs/42' с опечаткой скомпилируется и сломается только при клике. useParams вернёт id как string | undefined, и приходится проверять то, что по структуре маршрута есть всегда. TanStack Router выводит из дерева маршрутов union всех допустимых путей и точный тип параметров каждого.
Эффект прямой: при переименовании пути или параметра ломается билд, а не продакшен. Автодополнение редактора предлагает только существующие маршруты, а забытый или лишний параметр подсвечивается до запуска. Навигация перестаёт быть набором строк, в которые легко внести опечатку.
Типы здесь не пишутся руками. Маршруты регистрируются в одном дереве, и Router генерирует из него типы (часто через плагин сборки и declaration merging). Разработчик описывает маршруты один раз, а проверку ссылок получает автоматически.
Что именно даёт типобезопасность маршрутов в TanStack Router?
Search-параметры как типизированное состояние
В большинстве роутеров query-строка это сырой текст: ?page=2&sort=name достаётся как строки, и каждый компонент сам их парсит, проверяет и приводит к числам. TanStack Router делает search-параметры первоклассным типизированным состоянием. У маршрута объявляется схема валидации (часто через Zod), и Router сам разбирает строку в типизированный объект, а при навигации сериализует объект обратно.
Это превращает URL в надёжный источник состояния. Фильтры, сортировка и пагинация хранятся в адресной строке, переживают перезагрузку и расшариваются ссылкой, при этом остаются типизированными во всём приложении. Никакого ручного parseInt и проверок на пустую строку.
catch в схеме Zod задаёт безопасное значение по умолчанию, если в URL пришёл мусор. Так чужая или устаревшая ссылка с ?page=abc не уронит страницу, а тихо откатится к page=1.
Чем подход TanStack Router к search-параметрам отличается от обычной работы с query-строкой?
Loaders: данные на уровне маршрута и борьба с водопадами
Привычный водопад запросов выглядит так: переходим на маршрут, монтируется родительский компонент, его useEffect грузит данные, после ответа рендерится дочерний, его useEffect грузит свои данные - и так по цепочке. Каждый уровень ждёт предыдущего, хотя запросы независимы. Loader разрывает эту цепочку: данные привязываются к маршруту и начинают грузиться в момент перехода, ещё до рендера компонентов.
Loader получает уже провалидированные params и search, поэтому запрос формируется типобезопасно. ensureQueryData кладёт результат в кэш TanStack Query, а компонент затем читает тот же ключ через useQuery и получает данные мгновенно, без второго сетевого запроса. Router и Query делят один кэш, а не дублируют загрузку.
| Подход | Когда стартует запрос | Водопад |
|---|---|---|
| useEffect в компоненте | После монтирования компонента | Да, по уровням дерева |
| Loader маршрута | При переходе, до рендера | Нет, параллельно для уровня |
| Loader + Query | При переходе, результат в общем кэше | Нет, плюс дедупликация |
Loader не заменяет TanStack Query, а дополняет его. Loader решает когда запустить запрос (при переходе), а Query решает как кэшировать и обновлять результат. Без общего кэша легко получить двойную загрузку: один раз в loader, второй в компоненте.
Как loader маршрута помогает против водопадов запросов?
Связь с другими темами
Этот урок про типобезопасный роутинг. Рядом стоят соседние слои экосистемы:
- TanStack Query — Loader маршрута предзагружает данные в кэш Query, а компонент затем читает их через useQuery без повторного запроса
- Next.js App Router — Альтернативная модель: файловый роутинг и серверная загрузка. TanStack Router решает ту же задачу на клиенте с акцентом на типы
Итог
- TanStack Router строит из дерева маршрутов точные типы путей, параметров и search, поэтому навигация проверяется на этапе компиляции
- Неверная ссылка или забытый параметр не компилируются - целый класс рантайм-ошибок навигации исчезает
- Search-параметры это типизированный объект со схемой и сериализацией, а не сырые строки, которые каждый парсит руками
- Loaders грузят данные на уровне маршрута: запрос стартует при переходе, а не после монтирования компонента, что убирает водопады
- В связке с TanStack Query loader предзагружает данные в общий кэш, и компонент получает их без второго запроса
- В отличие от типонеосведомлённого по умолчанию React Router, здесь типобезопасность сквозная и обеспечивается компилятором
Связанные уроки
- rc-35-nextjs-app-router — App Router задаёт идею файлового роутинга и загрузки на сервере, на фоне которой понятен подход TanStack Router
- rc-36-tanstack-query — Loaders маршрута опираются на кэш TanStack Query, чтобы данные не запрашивались дважды