React Router: Полное руководство по маршрутизации в React-приложениях

Подробное руководство по React Router: от установки и базовых маршрутов до продвинутых техник, таких как вложенные маршруты, URL-параметры, lazy loading и защита страниц с помощью loaders.

Что такое React Router и зачем он нужен

React Router — это стандартная библиотека для организации навигации в React-приложениях. Она позволяет создавать одностраничные приложения (SPA) с несколькими виртуальными страницами, не перезагружая браузер. Без неё React-приложение было бы ограничено одним экраном, а любой переход требовал бы полной перезагрузки страницы.

Основные возможности React Router:

  • Создание множества страниц в рамках одного SPA.
  • Обработка URL-параметров и строк запроса.
  • Управление историей браузера и навигацией.
  • Создание вложенных маршрутов и макетов.
  • Реализация защищённых маршрутов для аутентификации.

Библиотека использует History API браузера для синхронизации интерфейса с URL-адресом. Это значит, что пользователь может использовать кнопки «Назад» и «Вперёд», а также добавлять страницы в закладки — всё работает как в обычном многостраничном сайте, но без перезагрузки.

Установка и настройка React Router

Для начала работы необходимо установить пакет. В современных проектах на React 18+ и React Router v7 используется единый пакет react-router. Установка выполняется через npm:

npm install react-router

После установки в файле package.json появится зависимость:

"dependencies": {
  "react": "^19.2.6",
  "react-dom": "^19.2.6",
  "react-router": "^7.15.1"
}

Обратите внимание: начиная с версии 7, пакет react-router-dom объединён с react-router, поэтому достаточно установить только один пакет. Для работы требуется Node.js не ниже 20-й версии и React 18+.

После установки приложение нужно обернуть в компонент BrowserRouter. Это корневой компонент, который включает маршрутизацию и использует History API. Пример минимальной настройки:

import { createRoot } from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router";

function Home() { return

# Главная

; }
function About() { return

# О нас

; }

createRoot(document.getElementById("app")).render(

      } />
      } />

);

BrowserRouter синхронизирует UI с URL, а Routes выбирает первый подходящий маршрут. Если ни один маршрут не совпал, можно добавить маршрут-заглушку с путём * для страницы 404.

Базовые компоненты: Link, Routes, Route

Для навигации между страницами React Router предоставляет три ключевых компонента:

  • Link — создаёт кликабельные ссылки, которые обновляют URL без перезагрузки страницы. Используется вместо обычных тегов ``.
  • Routes — контейнер для всех определений маршрутов. Он выбирает первый подходящий маршрут по порядку.
  • Route — определяет соответствие между URL-путем и React-компонентом.

Пример базовой навигации:

import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

function Home() { return

# Home Page

; }
function About() { return

# About Page

; }
function Contact() { return

# Contact Page

; }

function App() {
  return (

        Home |
        About |
        Contact

        } />
        } />
        } />

  );
}

Важно: Link не перезагружает страницу, а только меняет URL. Это ключевое отличие от обычных ссылок. Для стилизации активной ссылки используется компонент NavLink, который добавляет класс active или позволяет задать стили через функцию isActive.

Вложенные маршруты и компонент Outlet

Вложенные маршруты позволяют создавать страницы внутри страниц — например, раздел «Продукты» с подразделами «Автомобили» и «Мотоциклы». При этом общая часть страницы (шапка, меню) остаётся неизменной, а меняется только вложенный контент.

Для реализации вложенности используется компонент Outlet. Он указывает место, где будет отображаться содержимое дочернего маршрута.

Пример:

import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";

function Products() {
  return (

# Products Page

        Cars |
        Bikes

  );
}

function CarProducts() { return

## Cars

; }
function BikeProducts() { return

## Bikes

; }

function App() {
  return (

        }>
          } />
          } />

  );
}

При переходе на /products/car отображается страница Products с вложенным списком автомобилей. URL строится относительно родительского пути. Такой подход упрощает поддержку и масштабирование приложения, особенно когда много страниц с одинаковой структурой.

URL-параметры и хук useParams

URL-параметры позволяют создавать динамические маршруты, где часть пути может меняться. Например, страница профиля пользователя: /user/123, /user/456. В React Router для этого используется двоеточие перед именем параметра в пути маршрута.

Пример:

import { BrowserRouter, Routes, Route, Link, useParams } from "react-router-dom";

function Info() {
  const { firstname } = useParams();
  return

# Hello, {firstname}!

;
}

function App() {
  return (

        Emil |
        Tobias |
        Linus

        } />

  );
}

При переходе на /customer/Emil компонент Info получит параметр firstname со значением Emil. Хук useParams возвращает объект со всеми параметрами маршрута. Это удобно для создания страниц товаров, статей, профилей и любых других динамических страниц.

Важно: параметры маршрута — это часть пути, а не строка запроса. Для передачи дополнительных данных можно использовать строку запроса (query string), но для этого потребуется дополнительный парсинг.

Продвинутая маршрутизация: createBrowserRouter и loaders

Начиная с React Router v6.4, появился новый способ определения маршрутов — через функцию createBrowserRouter. Этот подход позволяет задавать маршруты в виде объектов и использовать встроенные механизмы, такие как loader и action.

loader — это функция, которая выполняется перед рендером страницы. Она может проверять права доступа, загружать данные или выполнять любую асинхронную логику. Если loader возвращает Promise, React Router может отложить рендер до его завершения, используя Suspense и Await.

Пример настройки роутера с loader:

import { createBrowserRouter, RouterProvider } from "react-router";

const router = createBrowserRouter([
  {
    path: "/steps",
    element: ,
    children: [
      {
        path: ":stepId",
        element: ,
        loader: loaderCheckExtension,
      },
    ],
  },
]);

function App() {
  return ;
}

Функция loaderCheckExtension может выглядеть так:

import { Params } from "react-router";

const cache = {};

const customCheck = () => new Promise((resolve) => {
  if (cache.isAlreadyChecked !== undefined) {
    resolve(cache.isAlreadyChecked);
  } else {
    setTimeout(() => {
      cache.isAlreadyChecked = true;
      resolve(true);
    }, 3000);
  }
});

export const loaderCheckExtension = ({ params }) => {
  const currentStep = params.stepId;
  if (currentStep === "greeting" || currentStep === "start") {
    return { hasExtension: true };
  }
  return { hasExtension: customCheck() };
};

Такой подход позволяет:

  • Централизованно управлять проверками (например, авторизацией).
  • Улучшить производительность за счёт отложенной загрузки.
  • Сделать код более читаемым и тестируемым.

В компоненте LazyStartPage данные из loader получаются через useLoaderData, а для ожидания асинхронной проверки используется Suspense и Await.

Защищённые маршруты и редиректы

Защищённые маршруты — это страницы, доступ к которым разрешён только после выполнения определённых условий (например, аутентификации или наличия расширения). В React Router это реализуется с помощью loader или компонента-обёртки.

Самый простой способ — использовать Navigate для перенаправления:

import { Navigate, Route } from "react-router";

function ProtectedRoute({ children }) {
  const isAuthenticated = checkAuth(); // ваша проверка
  return isAuthenticated ? children : ;
}

} />

Более продвинутый вариант — использовать loader, который возвращает редирект:

import { redirect } from "react-router";

export const authLoader = () => {
  const token = localStorage.getItem("token");
  if (!token) {
    return redirect("/login");
  }
  return null;
};

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

Также можно комбинировать loader с Suspense для отображения спиннера во время проверки. Это улучшает пользовательский опыт, особенно если проверка требует времени (например, запрос к серверу).

Стилизация активных ссылок с помощью NavLink

Компонент NavLink — это расширение Link, которое автоматически определяет, является ли текущий URL активным. Это удобно для подсветки текущего пункта меню, хлебных крошек или табов.

NavLink принимает функцию в качестве значения атрибута style или className. Эта функция получает объект с полем isActive (булево значение).

Пример стилизации:

import { NavLink } from "react-router-dom";

const navLinkStyles = ({ isActive }) => ({
  color: isActive ? '#007bff' : '#333',
  textDecoration: isActive ? 'none' : 'underline',
  fontWeight: isActive ? 'bold' : 'normal',
  padding: '5px 10px'
});

function Navigation() {
  return (

      Home
      About
      Contact

  );
}

NavLink также поддерживает атрибут className с функцией, что позволяет использовать CSS-классы вместо инлайн-стилей. Это делает код чище и легче поддерживаемым.

Кроме isActive, объект содержит поле isPending, которое можно использовать для индикации загрузки при ленивой загрузке маршрутов.

Обработка ошибок и страница 404

В любом приложении должны быть обработаны случаи, когда пользователь переходит по несуществующему адресу. В React Router для этого используется маршрут-заглушка с путём *.

Пример:


  } />
  } />
  } />

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

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

const router = createBrowserRouter([
  {
    path: "/",
    element: ,
    errorElement: ,
    children: [
      { path: "dashboard", element:  },
    ],
  },
]);

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

Оптимизация производительности: lazy loading и Suspense

В больших приложениях загрузка всех страниц сразу может замедлить начальную загрузку. React Router поддерживает ленивую загрузку (lazy loading) с помощью React.lazy и Suspense.

Пример:

import { lazy, Suspense } from "react";

const LazyAbout = lazy(() => import("./pages/About"));
const LazyContact = lazy(() => import("./pages/Contact"));

function App() {
  return (

      Загрузка...

}>

          } />
          } />

  );
}

Компонент Suspense отображает fallback (например, спиннер) до тех пор, пока лениво загруженный компонент не будет готов. Это значительно улучшает время загрузки начальной страницы.

В сочетании с loader из createBrowserRouter можно добиться ещё более тонкой оптимизации: loader может возвращать Promise, а компонент с помощью Await и Suspense показывать индикатор загрузки только для той части страницы, которая требует данных.

Такой подход особенно полезен для страниц с тяжёлыми данными (таблицы, графики) или для страниц, требующих проверки прав доступа.

Вопросы и ответы

В чём разница между BrowserRouter и HashRouter?

BrowserRouter использует History API браузера для работы с URL (например, /about). Он требует настройки сервера для обработки всех путей (иначе при прямой ссылке будет 404). HashRouter использует хеш-часть URL (например, /#/about) и не требует серверной настройки, но менее дружелюбен для SEO и не поддерживает серверный рендеринг.

Как передать дополнительные данные через Link?

Можно использовать атрибут state компонента Link: Профиль. В целевом компоненте данные доступны через useLocation().state. Это удобно для передачи контекста навигации (например, откуда пришёл пользователь).

Что такое loader в React Router и зачем он нужен?

loader — это функция, которая выполняется перед рендером маршрута. Она может загружать данные, проверять авторизацию или выполнять любую асинхронную логику. Результат loader доступен в компоненте через хук useLoaderData. Это позволяет централизовать подготовку данных и улучшить производительность за счёт отложенной загрузки.

Как сделать редирект после входа в систему?

Используйте компонент Navigate с атрибутом replace для перенаправления: `. В loader можно вернуть redirect('/dashboard') из react-router. Для сохранения исходного URL перед редиректом используйте state в Link или useLocation`.

Можно ли использовать React Router с TypeScript?

Да, React Router полностью поддерживает TypeScript. Вы можете типизировать параметры маршрутов, данные из loader и action. Например, useParams<{ id: string }>() или useLoaderData() as MyDataType. Это улучшает читаемость и снижает количество ошибок.

Как обработать ошибку в loader?

В loader можно выбросить исключение или вернуть объект с ошибкой. Если используется createBrowserRouter, можно задать errorElement для маршрута — он отобразится при любой ошибке в loader или рендере. Также можно использовать try/catch внутри loader и возвращать кастомный объект.

Что такое Outlet и как он работает?

Outlet — это компонент, который указывает место для отображения дочернего маршрута при вложенной маршрутизации. Родительский компонент рендерит общую часть страницы (шапку, меню), а Outlet заменяется содержимым дочернего маршрута. Это позволяет создавать сложные макеты без дублирования кода.