Route Guards: защита маршрутов

Route Guard — паттерн клиентской защиты маршрутов, при котором перед отображением страницы проверяется условие (авторизация, роль пользователя) и при несоответствии выполняется редирект.

Зачем нужно

В SPA без route guards пользователь может вручную ввести URL /admin или /dashboard и увидеть страницу или её скелет до проверки прав. Route Guards перехватывают переход и перенаправляют на /login или /403 до рендеринга защищённого контента. Важно понимать, что это клиентская защита — реальная авторизация проверяется на сервере при каждом API-запросе.

Где используется

  • Защита авторизованных маршрутов (дашборд, профиль, настройки)
  • Разграничение по ролям (admin, moderator, user)
  • Редирект авторизованных пользователей от /login на /dashboard
  • Защита WIP-страниц флагами фич (feature flags)

PrivateRoute в React Router

import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from './hooks/useAuth';

// Компонент-guard: проверяет авторизацию перед рендером дочерних маршрутов
function PrivateRoute() {
  const { user, isLoading } = useAuth;

  if (isLoading) {
    // Пока проверяем авторизацию — показываем загрузку, не рендерим контент
    return <div>Проверка авторизации...</div>;
  }

  if (!user) {
    // Не авторизован — редирект на login
    // state сохраняет путь для возврата после входа
    return <Navigate to="/login" state={{ from: location.pathname }} replace />;
  }

  // Авторизован — рендерим вложенные маршруты
  return <Outlet />;
}

// Использование в роутере
function AppRouter() {
  return (
    <Routes>
      {/* Публичные маршруты */}
      <Route path="/" element={<Home />} />
      <Route path="/login" element={<Login />} />
      <Route path="/about" element={<About />} />

      {/* Защищённые маршруты — все внутри PrivateRoute */}
      <Route element={<PrivateRoute />}>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
        <Route path="/settings" element={<Settings />} />
      </Route>

      {/* Защита по роли */}
      <Route element={<RoleGuard requiredRole="admin" />}>
        <Route path="/admin" element={<AdminPanel />} />
        <Route path="/admin/users" element={<AdminUsers />} />
      </Route>
    </Routes>
  );
}

Guard по ролям

function RoleGuard({ requiredRole }) {
  const { user } = useAuth;

  if (!user) {
    return <Navigate to="/login" replace />;
  }

  if (!user.roles.includes(requiredRole)) {
    return <Navigate to="/403" replace />;
  }

  return <Outlet />;
}

Возврат на страницу после входа

function Login() {
  const navigate = useNavigate;
  const location = useLocation;
  const { login } = useAuth;

  const handleLogin = async (credentials) => {
    await login(credentials);
    // Вернуть на страницу, с которой пришли
    const from = location.state?.from || '/dashboard';
    navigate(from, { replace: true });
  };

  return <LoginForm onSubmit={handleLogin} />;
}

Частые ошибки

  • Полагаются только на клиентскую защиту — route guards защищают UX, но не данные; API должен проверять токен при каждом запросе независимо от клиента.
  • Не показывают loading state — рендерят защищённый контент на миллисекунду до проверки авторизации (мерцание); всегда обрабатывайте isLoading.
  • Бесконечный цикл редиректа — если AuthRoute редиректит на /login, а /login снова проходит через AuthRoute.

Связанные темы

Ресурсы