Domain-driven routing

Domain-driven routing — подход к построению маршрутизации, при котором структура маршрутов отражает предметную область приложения, а не только URL-пути. TanStack Router предоставляет гибкие инструменты для реализации такого подхода, позволяя создавать маршруты, тесно связанные с бизнес-логикой.


Структура маршрутов и доменные модули

В основе Domain-driven routing лежит идея разделения маршрутов по доменным модулям. Каждый модуль отвечает за отдельную область приложения: пользователи, продукты, заказы и т.д. Такой подход повышает читаемость, поддерживаемость кода и позволяет легко масштабировать приложение.

import { createRouter, createRoute } from '@tanstack/router';

const usersRoute = createRoute({
  path: '/users',
  component: UsersPage,
  children: [
    createRoute({ path: '/:userId', component: UserProfile }),
    createRoute({ path: '/:userId/edit', component: UserEditPage }),
  ],
});

const productsRoute = createRoute({
  path: '/products',
  component: ProductsPage,
  children: [
    createRoute({ path: '/:productId', component: ProductDetail }),
    createRoute({ path: '/new', component: ProductCreate }),
  ],
});

const router = createRouter({
  routeTree: [usersRoute, productsRoute],
});

Ключевой момент: маршруты сгруппированы по функциональным областям, а вложенные маршруты отражают иерархию данных (например, пользователь → профиль → редактирование).


Использование маршрутов с данными

TanStack Router поддерживает загрузку данных на уровне маршрута через loader и использование params для передачи доменной информации:

const userDetailRoute = createRoute({
  path: '/users/:userId',
  component: UserProfile,
  loader: async ({ params }) => {
    const user = await fetchUserById(params.userId);
    return { user };
  },
});

const productDetailRoute = createRoute({
  path: '/products/:productId',
  component: ProductDetail,
  loader: async ({ params }) => {
    const product = await fetchProductById(params.productId);
    return { product };
  },
});

Преимущество: маршруты сами отвечают за загрузку необходимых данных, а компоненты получают их через контекст маршрутизатора, что уменьшает зависимость от глобального состояния.


Вложенные маршруты и outlet

TanStack Router позволяет строить глубокую вложенную структуру, что идеально подходит для доменно-ориентированной архитектуры. Вложенные маршруты используют outlet для рендеринга дочерних компонентов:

function UsersPage() {
  return (
    

Пользователи

); } function UserProfile({ loaderData }) { return (

{loaderData.user.name}

Email: {loaderData.user.email}

); }

Важно: дочерние маршруты наследуют контекст родительского маршрута, включая данные и параметры, что упрощает работу с вложенными сущностями.


Typed Routing и безопасная работа с параметрами

Использование TypeScript в сочетании с TanStack Router позволяет добиться строгой типизации параметров маршрутов и данных:

interface UserParams {
  userId: string;
}

const userDetailRoute = createRoute({
  path: '/users/:userId',
  component: UserProfile,
  loader: async ({ params }) => {
    const user = await fetchUserById(params.userId);
    return { user };
  },
});

Эффект: ошибки типа в параметрах маршрутов или при получении данных становятся видимыми на этапе компиляции, снижая вероятность runtime-ошибок.


Примеры паттернов Domain-driven Routing

  1. Сценарий CRUD: каждая доменная сущность получает набор маршрутов для просмотра, создания, редактирования и удаления.
  2. Контекстные маршруты: вложенные маршруты под родительским контекстом (например, заказы пользователя) для логически связанных данных.
  3. Ленивая загрузка: отдельные доменные модули могут загружаться динамически через lazy import, минимизируя начальный вес приложения.
const ordersRoute = createRoute({
  path: '/users/:userId/orders',
  component: OrdersPage,
  loader: async ({ params }) => fetchOrdersForUser(params.userId),
});

Управление состоянием маршрутов

TanStack Router предоставляет возможность хранить и управлять состоянием на уровне маршрутов. Состояние маршрута может включать фильтры, сортировку или данные формы, что особенно полезно для сложных доменных интерфейсов:

const ordersRoute = createRoute({
  path: '/orders',
  component: OrdersPage,
  useLoaderData: true,
  defaultSearchParams: {
    status: 'all',
  },
});

Вывод: состояние маршрута тесно связано с предметной областью и автоматически синхронизируется с URL, облегчая поддержку истории и навигации.