Защита вложенных маршрутов

TanStack Router предоставляет гибкую систему маршрутизации, позволяющую строить сложные многоуровневые маршруты в приложениях на JavaScript. Одним из важных аспектов построения маршрутов является защита вложенных маршрутов, чтобы ограничить доступ к определённым страницам только для авторизованных пользователей или пользователей с конкретными правами.

Концепция вложенных маршрутов

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

const routes = [
  {
    path: '/',
    component: HomePage,
    children: [
      {
        path: 'dashboard',
        component: DashboardPage,
      },
      {
        path: 'profile',
        component: ProfilePage,
      },
    ],
  },
];

Каждый дочерний маршрут может быть защищён независимо, либо наследовать защиту родителя.

Создание защищённого маршрута

Для защиты маршрутов в TanStack Router используется механизм загрузчиков (loader) и хуков для проверки доступа. Наиболее распространённая стратегия — проверка авторизации перед рендерингом компонента:

import { redirect } from '@tanstack/router';

function authLoader() {
  const user = getCurrentUser();
  if (!user) {
    throw redirect('/login');
  }
  return user;
}

В этом примере authLoader проверяет наличие пользователя. Если пользователь не авторизован, выполняется перенаправление на страницу входа. Этот loader можно подключить к маршруту:

const routes = [
  {
    path: '/',
    component: HomeLayout,
    children: [
      {
        path: 'dashboard',
        component: DashboardPage,
        loader: authLoader,
      },
      {
        path: 'profile',
        component: ProfilePage,
        loader: authLoader,
      },
    ],
  },
];

Наследование защиты для вложенных маршрутов

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

const routes = [
  {
    path: '/',
    component: HomeLayout,
    loader: authLoader, // защита применяется ко всем детям
    children: [
      {
        path: 'dashboard',
        component: DashboardPage,
      },
      {
        path: 'profile',
        component: ProfilePage,
      },
    ],
  },
];

В этом случае проверка выполняется один раз при входе на любой дочерний маршрут.

Ограничение доступа по ролям

Для более тонкой защиты можно реализовать проверку ролей пользователя:

function roleLoader(requiredRole) {
  return () => {
    const user = getCurrentUser();
    if (!user || user.role !== requiredRole) {
      throw redirect('/unauthorized');
    }
    return user;
  };
}

Применение:

const routes = [
  {
    path: '/admin',
    component: AdminLayout,
    loader: roleLoader('admin'),
    children: [
      {
        path: 'settings',
        component: AdminSettingsPage,
      },
    ],
  },
];

Теперь доступ к /admin/settings будет только у пользователей с ролью admin.

Ленивая загрузка компонентов и защита маршрутов

TanStack Router поддерживает ленивую загрузку компонентов, что позволяет совместить защиту маршрутов с оптимизацией загрузки:

import { lazy } from 'react';

const DashboardPage = lazy(() => import('./DashboardPage'));

const routes = [
  {
    path: '/dashboard',
    component: DashboardPage,
    loader: authLoader,
  },
];

Loader выполняется до загрузки компонента, что предотвращает рендеринг защищённой страницы без авторизации.

Комбинация защиты и переходов по умолчанию

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

const routes = [
  {
    path: '/app',
    component: AppLayout,
    loader: authLoader,
    children: [
      {
        path: '/',
        redirect: 'dashboard', // при переходе на /app редирект на /app/dashboard
      },
      {
        path: 'dashboard',
        component: DashboardPage,
      },
    ],
  },
];

Такое решение сохраняет защиту маршрута и улучшает пользовательский опыт, автоматически направляя на корректную дочернюю страницу.

Обработка ошибок доступа

Для лучшего контроля над защитой можно использовать компонент ErrorBoundary, чтобы отлавливать ошибки типа redirect и показывать пользовательские страницы:

function AuthErrorBoundary({ error }) {
  if (error.status === 302) {
    return <RedirectPage />;
  }
  return <ErrorPage message={error.message} />;
}

const routes = [
  {
    path: '/dashboard',
    component: DashboardPage,
    loader: authLoader,
    errorComponent: AuthErrorBoundary,
  },
];

Это позволяет централизованно обрабатывать все ошибки доступа, включая редиректы и сообщения об отказе в доступе.

Практические рекомендации

  • Использовать loader на уровне родителя, если все дочерние маршруты имеют одинаковые требования к доступу.
  • Для сложных сценариев с разными ролями создавать отдельные loader-функции, передавая роль как параметр.
  • Ленивая загрузка компонентов в сочетании с loader повышает производительность и безопасность.
  • ErrorBoundary облегчает управление редиректами и сообщениями об ошибках.
  • Проверка доступа на уровне маршрутов должна быть синхронной или асинхронной через loader, чтобы исключить рендеринг запрещённых компонентов.

Такой подход обеспечивает безопасное управление вложенными маршрутами и позволяет строить масштабируемые приложения с чётким разграничением прав доступа.