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

Приватные маршруты (private routes) используются для ограничения доступа к определённым страницам приложения. Обычно это страницы, доступ к которым должен быть только у аутентифицированного пользователя, например, профиль, личные настройки или корзина в интернет-магазине. Реализация приватных маршрутов в React Router требует понимания работы с компонентами Route, Navigate и состояния аутентификации.


Основные концепции

1. Контроль доступа через компонент-обертку Для приватного маршрута создается компонент, который проверяет, авторизован ли пользователь. Если нет — происходит перенаправление на страницу входа (/login).

Пример базового компонента приватного маршрута:

import { Navigate, Outlet } from 'react-router-dom';

function PrivateRoute({ isAuthenticated }) {
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  return <Outlet />;
}

export default PrivateRoute;
  • isAuthenticated — логическое значение, отражающее состояние аутентификации.
  • Navigate выполняет программное перенаправление без перезагрузки страницы.
  • Outlet позволяет вложенным маршрутам отображаться внутри приватного маршрута.

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

Для организации маршрутов с React Router v6+ применяется структура с Routes и вложенными Route. Приватные маршруты используют обертку-компонент.

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import Dashboard from './Dashboard';
import Login from './Login';
import Home from './Home';

function App({ isAuthenticated }) {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route element={<PrivateRoute isAuthenticated={isAuthenticated} />}>
          <Route path="/dashboard" element={<Dashboard />} />
        </Route>
      </Routes>
    </Router>
  );
}

Ключевой момент: приватный маршрут оборачивает все защищенные страницы. Если пользователь не авторизован, доступ к /dashboard невозможен.


Защита нескольких маршрутов

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

<Route element={<PrivateRoute isAuthenticated={isAuthenticated} />}>
  <Route path="/profile" element={<Profile />} />
  <Route path="/settings" element={<Settings />} />
  <Route path="/orders" element={<Orders />} />
</Route>

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


Передача состояния авторизации через контекст

Вместо передачи пропса isAuthenticated напрямую, удобно использовать контекст React для хранения состояния пользователя:

import { createContext, useContext, useState } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const login = () => setIsAuthenticated(true);
  const logout = () => setIsAuthenticated(false);

  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  return useContext(AuthContext);
}

Теперь приватный маршрут можно реализовать через контекст:

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

function PrivateRoute() {
  const { isAuthenticated } = useAuth();
  if (!isAuthenticated) return <Navigate to="/login" replace />;
  return <Outlet />;
}

Использование контекста упрощает доступ к состоянию авторизации в любом компоненте приложения.


Динамическое перенаправление после логина

Чтобы вернуть пользователя на страницу, к которой он пытался получить доступ, можно передавать параметр state в Navigate:

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

function PrivateRoute() {
  const { isAuthenticated } = useAuth();
  const location = useLocation();

  if (!isAuthenticated) {
    return <Navigate to="/login" replace state={{ from: location }} />;
  }

  return <Outlet />;
}

На странице логина:

import { useLocation, useNavigate } from 'react-router-dom';
import { useAuth } from './AuthContext';

function Login() {
  const { login } = useAuth();
  const navigate = useNavigate();
  const location = useLocation();
  const from = location.state?.from?.pathname || '/';

  const handleLogin = () => {
    login();
    navigate(from, { replace: true });
  };

  return <button onCl ick={handleLogin}>Войти</button>;
}

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


Обработка маршрутов при роли пользователя

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

function RoleBasedRoute({ allowedRoles }) {
  const { user } = useAuth();
  if (!user || !allowedRoles.includes(user.role)) {
    return <Navigate to="/unauthorized" replace />;
  }
  return <Outlet />;
}

Пример использования:

<Route element={<RoleBasedRoute allowedRoles={['admin']} />}>
  <Route path="/admin" element={<AdminPanel />} />
</Route>

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


Резюме практических приёмов

  • Использование Outlet для вложенных маршрутов позволяет минимизировать дублирование.
  • Navigate обеспечивает удобное программное перенаправление.
  • Контекст для авторизации упрощает управление состоянием.
  • Передача state при перенаправлении позволяет реализовать «возврат к исходному маршруту» после логина.
  • Комбинирование с ролевой проверкой позволяет создавать сложные системы доступа.

Приватные маршруты в React Router — это гибкий инструмент, позволяющий строить безопасные интерфейсы и управлять доступом к страницам приложения, не нарушая реактивную модель и SPA-архитектуру.