ДокументацияReactReact Router — маршрутизация
Средний 12 мин чтения

React Router — маршрутизация

Маршрутизация в React-приложении: React Router v7, nested routes, динамические параметры, guards, lazy loading страниц.

React RouterмаршрутизацияSPAnested routesReact

Установка

npm install react-router-dom

React Router v7 — текущая мажорная версия. API стабилизировалось, основные хуки и компоненты те же, что и в v6.

Базовая настройка

// src/main.tsx
import { BrowserRouter } from 'react-router-dom'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
)
// src/App.tsx
import { Routes, Route } from 'react-router-dom'
import { Home } from '@/pages/Home'
import { About } from '@/pages/About'
import { NotFound } from '@/pages/NotFound'

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  )
}
import { Link, NavLink } from 'react-router-dom'

function Navbar() {
  return (
    <nav>
      {/* Link — обычная ссылка без стилей */}
      <Link to="/">Главная</Link>

      {/* NavLink — добавляет класс активному пункту */}
      <NavLink
        to="/about"
        className={({ isActive }) =>
          isActive ? 'text-blue-600 font-bold' : 'text-gray-500'
        }
      >
        О нас
      </NavLink>
    </nav>
  )
}

Nested routes (вложенные маршруты)

Вложенные маршруты позволяют компоненту-родителю рендерить дочерние через <Outlet />:

import { Outlet } from 'react-router-dom'

// src/layouts/MainLayout.tsx
function MainLayout() {
  return (
    <div>
      <Navbar />
      <main>
        <Outlet />
      </main>
      <Footer />
    </div>
  )
}
// src/App.tsx
function App() {
  return (
    <Routes>
      <Route element={<MainLayout />}>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/blog" element={<Blog />} />
      </Route>
    </Routes>
  )
}

Главная, О нас и Блог будут обёрнуты в MainLayout — шапка и подвал не дублируются.

Динамические параметры

// Маршрут с параметром
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />

Чтение параметров через useParams:

import { useParams } from 'react-router-dom'

function UserProfile() {
  const { userId } = useParams()

  return <h1>Профиль пользователя {userId}</h1>
}

Типизация параметров:

interface RouteParams {
  userId: string
}

function UserProfile() {
  const { userId } = useParams<RouteParams>()
  // userId: string | undefined
}

Query-параметры

import { useSearchParams } from 'react-router-dom'

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams()
  const category = searchParams.get('category') ?? 'all'
  const page = searchParams.get('page') ?? '1'

  function handleFilter(cat: string) {
    setSearchParams({ category: cat, page: '1' })
  }

  return (
    <div>
      <p>Категория: {category}, страница: {page}</p>
      <button onClick={() => handleFilter('electronics')}>Электроника</button>
    </div>
  )
}

Программная навигация

import { useNavigate } from 'react-router-dom'

function LoginForm() {
  const navigate = useNavigate()

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    // ... логика входа
    navigate('/dashboard')
  }

  return <form onSubmit={handleSubmit}>...</form>
}

navigate принимает число для навигации по истории:

navigate(-1)   // Назад
navigate(-2)   // На две страницы назад
navigate(1)    // Вперёд

Защищённые маршруты (guards)

import { Navigate, useLocation } from 'react-router-dom'

function PrivateRoute({ children }: { children: React.ReactNode }) {
  const { user } = useAuth()
  const location = useLocation()

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

  return <>{children}</>
}

Использование:

<Routes>
  <Route element={<MainLayout />}>
    <Route path="/" element={<Home />} />
    <Route path="/login" element={<Login />} />
    <Route
      path="/dashboard"
      element={
        <PrivateRoute>
          <Dashboard />
        </PrivateRoute>
      }
    />
  </Route>
</Routes>

После входа можно вернуть пользователя на ту же страницу:

function Login() {
  const navigate = useNavigate()
  const location = useLocation()
  const from = (location.state as { from?: Location })?.from?.pathname ?? '/'

  function handleLogin() {
    // ... логика входа
    navigate(from, { replace: true })
  }
}

Lazy loading страниц

Крупные страницы и компоненты лучше подгружать по необходимости:

import { lazy, Suspense } from 'react'

const Home = lazy(() => import('@/pages/Home'))
const About = lazy(() => import('@/pages/About'))
const Dashboard = lazy(() => import('@/pages/Dashboard'))

function App() {
  return (
    <Suspense fallback={<div>Загрузка...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  )
}

Каждая страница будет в отдельном чанке и загрузится только при переходе.

Scroll to top

По умолчанию React Router не скроллит наверх при смене маршрута:

import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'

function ScrollToTop() {
  const { pathname } = useLocation()

  useEffect(() => {
    window.scrollTo(0, 0)
  }, [pathname])

  return null
}

// Добавить внутри BrowserRouter
<BrowserRouter>
  <ScrollToTop />
  <App />
</BrowserRouter>

404 — страница не найдена

function NotFound() {
  return (
    <div>
      <h1>404</h1>
      <p>Такой страницы не существует</p>
      <Link to="/">На главную</Link>
    </div>
  )
}

// В Routes — последним маршрутом
<Route path="*" element={<NotFound />} />

Основные хуки React Router

ХукВозвращает
useParams()Параметры URL (:id, :slug)
useSearchParams()Query-параметры (?page=2)
useNavigate()Функция для программной навигации
useLocation()Текущий URL, pathname, state
useMatch()Информация о совпадении маршрута

Итог

React Router покрывает все базовые сценарии: вложенные маршруты через <Outlet />, динамические параметры через useParams, защиту страниц через обёртки, lazy loading через React.lazy(). Для SPA на Vite это стандартный выбор.