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

Защищенные маршруты — это маршруты, доступ к которым ограничен определенными условиями, чаще всего аутентификацией пользователя или проверкой его прав. В TanStack Router концепция защиты маршрутов строится на использовании проверок загрузчиков данных (loader functions) и хуков перехода (navigation hooks), что позволяет централизованно контролировать доступ к различным частям приложения.

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

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

import { createRoute } from '@tanstack/router';
import { requireAuth } from './auth';

const protectedRoute = createRoute({
  path: '/dashboard',
  component: DashboardPage,
  loader: async ({ context }) => {
    await requireAuth(context.user);
  },
});

Ключевые моменты:

  • loader выполняется перед рендером компонента.
  • В loader можно бросать ошибки (throw new Error) или перенаправлять пользователя (redirect('/login')) в случае отсутствия доступа.
  • Проверка аутентификации централизована и повторно используема для нескольких маршрутов.

Универсальная проверка доступа

Для повторного использования проверок удобно создавать отдельные функции проверки, которые можно использовать в нескольких маршрутах:

export async function requireAuth(user) {
  if (!user || !user.isAuthenticated) {
    throw redirect('/login');
  }
}

export async function requireAdmin(user) {
  if (!user || !user.isAdmin) {
    throw redirect('/unauthorized');
  }
}

Затем эти функции подключаются в соответствующих loader маршрутах:

const adminRoute = createRoute({
  path: '/admin',
  component: AdminPage,
  loader: async ({ context }) => {
    await requireAdmin(context.user);
  },
});

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

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

const protectedGroup = createRoute({
  path: '/app',
  loader: async ({ context }) => {
    await requireAuth(context.user);
  },
  children: [
    createRoute({ path: 'profile', component: ProfilePage }),
    createRoute({ path: 'settings', component: SettingsPage }),
  ],
});

В этом примере пользователь должен быть аутентифицирован, чтобы получить доступ к любой странице внутри /app.

Перехват переходов и условная навигация

TanStack Router предоставляет хуки для контроля навигации: onEnter и onLeave могут использоваться для динамической проверки прав пользователя и блокировки перехода при необходимости:

protectedRoute.onEnter(({ context }) => {
  if (!context.user.isAuthenticated) {
    return redirect('/login');
  }
});

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

Обработка ошибок и перенаправлений

TanStack Router позволяет обрабатывать ошибки и перенаправления, возникающие в loader, с помощью специального маршрута ошибок:

const rootRoute = createRoute({
  path: '/',
  component: RootPage,
  errorComponent: ErrorPage,
  children: [protectedRoute, adminRoute],
});
  • Любые исключения в loader автоматически передаются в errorComponent.
  • redirect позволяет мгновенно перенаправить пользователя без отображения компонента ошибки.

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

  1. Централизованная проверка: Вынесение логики аутентификации в отдельные функции повышает читаемость и снижает дублирование кода.
  2. Вложенные маршруты для групп: Защита группы маршрутов через родительский маршрут упрощает управление доступом.
  3. Асинхронные проверки: Использование асинхронных loader позволяет интегрировать проверку с сервером, токенами и внешними API.
  4. Гибкая обработка ошибок: Использование errorComponent и redirect обеспечивает корректное поведение при нарушении условий доступа.

Пример комплексной структуры маршрутов с защитой

const appRoutes = createRoute({
  path: '/',
  component: AppLayout,
  children: [
    createRoute({ path: 'login', component: LoginPage }),
    createRoute({
      path: 'dashboard',
      loader: async ({ context }) => await requireAuth(context.user),
      component: DashboardPage,
    }),
    createRoute({
      path: 'admin',
      loader: async ({ context }) => await requireAdmin(context.user),
      component: AdminPage,
    }),
  ],
});

В данной конфигурации:

  • Любой пользователь может попасть на страницу /login.
  • Страница /dashboard доступна только аутентифицированным пользователям.
  • Страница /admin доступна только пользователям с правами администратора.
  • Ошибки перенаправлений или доступа можно обработать единым errorComponent.