Когда вы только начинаете изучать React, кажется, что он умеет все. В нем есть множество встроенных инструментов, из которых можно собрать почти любой интерфейс. Однако стоит вам начать создавать многостраничное приложение — и вы столкнетесь с простыми задачами, которые React из коробки не решает.
Допустим, вы хотите сделать сайт-портфолио. На нем будет главная страница, страница «О себе» и список проектов. И вот вам необходимо сделать так, чтобы при клике на ссылку «О себе» в адресной строке появлялся путь /about, открывалась соответствующая страница, а браузер не запрашивал заново все ресурсы с сервера. React такую навигацию не делает — для этого нужна библиотека маршрутизации.
Самая популярная библиотека для маршрутизации — React Router. Она связывает URL в адресной строке браузера с React-компонентами и делает навигацию в одностраничных приложениях такой же удобной, как на обычных многостраничных сайтах — с поддержкой кнопок «Назад» и «Вперед», прямых ссылок и закладок.
В статье мы познакомимся с библиотекой React Router и разберемся, как с ней работать. Сначала будет немного теории, а после мы соберем вот такой сайт-портфолио с несколькими страницами.
Материал подойдет всем, кто знаком с базовыми концепциями React или готов разобраться с ними с помощью официальной документации. Вам достаточно понимать, что такое компоненты, как работают хуки и состояние. Также понадобятся знания основ JavaScript, разметки и стилизации.
Зачем React-приложениям нужна маршрутизация
Для начала вспомним, как работают обычные сайты и чем от них отличается React-приложение. Классический многостраничный сайт работает так: пользователь кликает по ссылке, браузер отправляет HTTP-запрос на сервер, сервер формирует HTML-страницу и отдает ее вместе со всеми ресурсами — стилями, скриптами и изображениями. Каждый URL соответствует отдельному HTML-документу на сервере, и при любом переходе по ссылке происходит перезагрузка — браузер заново загружает все полученные файлы и отрисовывает новую страницу с нуля.

React-приложение — это одностраничное приложение, поэтому при первой загрузке браузер получает один HTML-файл и набор JavaScript-файлов. После этого управление интерфейсом полностью берет на себя JavaScript. Все дальнейшие изменения на странице происходят без обращения к серверу за новым HTML — вместо этого React динамически обновляет DOM и перерисовывает только те компоненты, которые действительно изменились. Поскольку полной перезагрузки страницы не происходит, навигация на сайте становится практически мгновенной.
Но проблема в том, что браузер ничего не знает о ваших «страницах» — для него существует только один адрес, который загружен изначально. Если вы нажмете кнопку «Назад», то вернетесь на предыдущий сайт, а не на предыдущую страницу. А если скопируете ссылку на текущее состояние и попробуете открыть ее в новой вкладке — снова загрузится только главная, потому что URL остался прежним.
И вот чтобы перемещаться между страницами React-приложения, как раз и нужен механизм маршрутизации. Он связывает URL в адресной строке с тем, что отображается на экране. React эту задачу не решает, потому что его задача — рендерить компоненты. А вот библиотеки вроде React Router как раз на этом специализируются — они отслеживают изменения URL, сопоставляют их с заданными маршрутами и сообщают React, какие компоненты нужно показать.

Что умеет React Router и как им пользоваться
В начале 2026 года актуальна седьмая версия React Router, которая предлагает три способа работы с библиотекой: Framework Mode, Data Mode и Declarative Mode.
Framework Mode — полноценный фреймворк поверх React Router. Он берет на себя не только маршрутизацию, но и рендеринг на сервере (SSR), загрузку данных, оптимизацию сборки и развертывание. По сути, это эволюция Remix — другого фреймворка для React от той же команды разработчиков. До 2024 года это был отдельный проект, но в 2024 году произошло слияние Remix и React Router, и теперь Framework Mode React Router v7 включает в себя все возможности Remix.
Этот режим предлагает решения для продакшн-приложений: автоматическое разделение кода, предзагрузку данных через loader-функции, оптимизированную гидратацию и встроенные инструменты для деплоя. Однако для новичков в React Framework Mode избыточен, поскольку скрывает детали за абстракциями и требует понимания серверного рендеринга, архитектуры full-stack приложений и lifecycle hooks. Все это сильно усложняет изучение базовых принципов маршрутизации.
Data Mode — режим, в котором React Router управляет не только навигацией, но и загрузкой данных для каждого маршрута через специальные loader-функции. Вы описываете, какие данные нужно загрузить перед рендером страницы, и роутер выполняет эти запросы, управляет состоянием загрузки и обрабатывает ошибки. Это удобно для сложных приложений с множеством запросов, но требует понимания асинхронности, промисов и жизненного цикла загрузки данных — все это пока не нужно новичку, который только осваивает базовую маршрутизацию.
Declarative Mode — самый простой и понятный режим. В нем вы только описываете маршруты в JSX с помощью компонентов, оборачиваете приложение в провайдер роутера — и готово. Не нужно настраивать серверный рендеринг или разбираться с загрузкой данных — только базовая маршрутизация. С этого режима начинали все предыдущие версии React Router, и его мы будем использовать далее в статье.

Собираем сайт‑портфолио и осваиваем базовые концепции роутера
Теория — это хорошо, но маршрутизацию лучше осваивать на практике. Поэтому далее мы пошагово создадим сайт с такими маршрутами:
- / — «Главная»;
- /about — «О себе»;
- /projects — «Проекты»;
- /projects/:id — страница конкретного проекта;
- * — страница 404 для несуществующих маршрутов.
Чтобы не отвлекаться на дизайн, в первую очередь мы сосредоточимся на концепциях роутинга. Поэтому содержимое сайта до последнего шага будет минимальным — текст и базовая разметка. В конце мы добавим стили и превратим заготовку в полноценный сайт-портфолио. Работать мы будем в редакторе Antigravity, вы же можете использовать VS Code или любой другой инструмент.
Шаг 1. Создаем проект и устанавливаем React Router
Для начала создадим новое React-приложение с помощью сборщика Vite. Для этого откройте терминал в редакторе кода и выполните команду:
npm create vite@latest dev-portfolio -- --template react
Эта команда создаст папку проекта с именем dev-portfolio. Добавьте ее в редактор, перейдите в нее в терминале и установите базовые зависимости:
cd dev-portfolio npm install
Теперь установим библиотеку React Router:
npm install react-router
Обратите внимание: в седьмой версии библиотеки весь нужный код собран в одном пакете react-router — отдельно устанавливать react-router-dom больше не нужно. Также все компоненты и хуки импортируются напрямую из react-router.
Запускаем наш проект:
npm run dev
Если вы все сделали правильно, в терминале появится сообщение с адресом локального сервера. Откройте этот адрес в браузере — вы увидите страницу с логотипами Vite и React. Это означает, что приложение запущено и готово к работе.

Чтобы проверить версии установленных зависимостей, откройте файл package.json в корне проекта и найдите раздел dependencies. Мы будем использовать react: ^19.2.0, react-dom: ^19.2.0 и react-router: ^7.13.0. У вас может быть другая конфигурация.

Шаг 2. Добавляем страницы-заглушки
Прежде чем настраивать маршрутизацию в коде приложения, давайте создадим компоненты для каждой страницы. Пока это будут простые заглушки — каждая вернет заголовок с названием страницы, чтобы мы могли проверить работу роутера.
Для этого в папке src создайте папку pages, а внутри нее — следующие четыре файла с расширением .jsx:
// src/pages/Home.jsx
export default function Home() {
return <h1>Главная</h1>;
}
// src/pages/About.jsx
export default function About() {
return <h1>О себе</h1>;
}
// src/pages/Projects.jsx
export default function Projects() {
return <h1>Проекты</h1>;
}
// src/pages/NotFound.jsx
export default function NotFound() {
return <h1>404 — Страница не найдена</h1>;
}
Шаг 3. Подключаем роутер и настраиваем маршруты
Найдите в папке src файл main.jsx и оберните приложение в BrowserRouter:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
)
BrowserRouter — это компонент, который дает доступ к функциям маршрутизации во всем приложении. Он использует встроенный браузерный инструмент HTML5 History API для синхронизации интерфейса с текущим URL и позволяет изменять адресную строку без перезагрузки страницы. Для большей ясности представьте BrowserRouter как Wi-Fi-роутер в доме. Один раз подключив его на входе, все устройства внутри получают доступ к интернету. Так же и BrowserRouter — один раз обернув приложение, все компоненты внутри получают доступ к маршрутизации.
После подключения BrowserRouter в main.jsx откройте файл App.jsx в папке src и настройте маршруты с помощью компонентов Routes и Route. Для этого удалите код, который сборщик Vite сгенерировал автоматически, и замените его на следующий:
import { Routes, Route } from 'react-router'
import Home from './pages/Home'
import About from './pages/About'
import Projects from './pages/Projects'
import NotFound from './pages/NotFound'
export default function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/projects" element={<Projects />} />
<Route path="*" element={<NotFound />} />
</Routes>
)
}
Разберем, что происходит в файле App.jsx:
- Routes — контейнер для всех маршрутов. Он анализирует текущий URL и отображает только тот компонент Route, путь которого совпадает с адресом.
- Route — описывает отдельный маршрут. Принимает два ключевых пропа: path — URL-адрес и element — компонент, который нужно показать.
- path=»*» — специальный маршрут-ловушка, который срабатывает, когда ни один другой маршрут не совпал. Мы его используем для страницы 404.
На этом этапе вы можете попробовать вручную вводить разные адреса в браузере: localhost:5173/about, localhost:5173/projects или localhost:5173/что-угодно — и вы увидите, как меняется содержимое страницы в зависимости от URL.

Шаг 4. Делаем навигацию через Link и NavLink
Вводить URL вручную неудобно, поэтому нам необходимо реализовать меню навигации. Однако важно учитывать, что для ссылок в этом меню мы не можем использовать обычный тег <a> — он вызывает полную перезагрузку страницы, что недопустимо в одностраничных приложениях. Поэтому, чтобы избежать перезагрузки, в React Router предусмотрен компонент Link. Им и воспользуемся.
В папке src создайте подпапку components, а в ней файл Navbar.jsx:
import { Link } from 'react-router'
export default function Navbar() {
return (
<nav>
<Link to="/">Главная</Link>
<Link to="/about">О себе</Link>
<Link to="/projects">Проекты</Link>
</nav>
)
}
Чтобы навигация отображалась на всех страницах, добавьте компонент Navbar в файл App.jsx. Расположите его над блоком Routes:
import { Routes, Route } from 'react-router'
import Navbar from './components/Navbar'
import Home from './pages/Home'
import About from './pages/About'
import Projects from './pages/Projects'
import NotFound from './pages/NotFound'
export default function App() {
return (
<>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/projects" element={<Projects />} />
<Route path="*" element={<NotFound />} />
</Routes>
</>
)
}
После добавления навигации в браузере появятся три слипшиеся ссылки вверху страницы, по которым можно кликать и переходить между разделами:

Обычно на сайтах принято визуально выделять активный пункт меню, который соответствует текущей странице. Для этого в React Router есть компонент NavLink — улучшенная версия Link, которая знает, активна ли ссылка в данный момент.
Здесь есть нюанс: NavLink автоматически добавляет CSS-класс .active к элементу, когда ссылка активна — это поведение работает по умолчанию. Поэтому самый простой способ стилизовать активную ссылку — написать в CSS правило .active { … } и не передавать никаких дополнительных пропсов в NavLink.
Однако для более тонкой настройки стилей вы можете передавать функцию в проп className. Эта функция будет получать объект с булевым свойством isActive, которое равно true, когда ссылка соответствует текущему URL. На основе этого значения можно динамически применять нужные CSS-классы или инлайн-стили.
Давайте добавим динамическое применение CSS-класса active через проп className и обновим файл Navbar.jsx:
import { NavLink } from 'react-router'
export default function Navbar() {
return (
<nav>
<NavLink
to="/"
className={({ isActive }) => isActive ? 'active' : ''}
end
>
Главная
</NavLink>
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'active' : ''}
>
О себе
</NavLink>
<NavLink
to="/projects"
className={({ isActive }) => isActive ? 'active' : ''}
>
Проекты
</NavLink>
</nav>
)
}
Обратите внимание еще на один момент: для ссылки «Главная» мы добавили проп end. Он указывает, что ссылка считается активной только при точном совпадении пути. Без этого пропа ссылка на / была бы активной на всех страницах, поскольку все пути в приложении начинаются с / (например, /about или /projects).
Когда вы обновите файл Navbar.jsx, откройте в браузере инспектор элементов, кликните на каждую ссылку в навигации и понаблюдайте, как класс active динамически применяется к активной ссылке и убирается с неактивных:

Шаг 5. Настраиваем динамические маршруты
Представьте, что со временем в портфолио добавится несколько десятков проектов. Создавать отдельный маршрут для каждого (/projects/1, /projects/2, /projects/3 и так далее) — не лучшая идея. Вам придется прописывать практически одинаковые маршруты для каждого проекта, что усложнит его поддержку. Вместо этого React Router предлагает использовать динамический сегмент пути с параметром :id, который сможет автоматически обрабатывать любой идентификатор проекта.
Добавьте в App.jsx новый маршрут:
<Route path="/projects/:id" element={<ProjectDetail />} />
Двоеточие перед id обозначает динамический параметр маршрута — React Router воспринимает эту часть пути как переменную и сохраняет ее значение для дальнейшего использования в компоненте. Благодаря этому любые URL вида /projects/42 или /projects/my-cool-app будут соответствовать одному маршруту, а значение после /projects/ станет доступно через хук useParams().
В папке pages создайте файл ProjectDetail.jsx — это будет страница проекта:
import { useParams } from 'react-router'
export default function ProjectDetail() {
const { id } = useParams()
return (
<div>
<h1>Проект #{id}</h1>
<p>Здесь будет описание проекта с идентификатором {id}</p>
</div>
)
}
Теперь обновите файл Projects.jsx и добавьте ссылки на отдельные проекты:
import { Link } from 'react-router'
const projects = [
{ id: 1, title: 'Интернет-магазин' },
{ id: 2, title: 'Погодный сервис'},
{ id: 3, title: 'Трекер задач' },
]
export default function Projects() {
return (
<div>
<h1>Проекты</h1>
<ul>
{projects.map(project => (
<li key={project.id}>
<Link to={`/projects/${project.id}`}>{project.title}</Link>
</li>
))}
</ul>
</div>
)
}
Этот код создаст список кликабельных ссылок на основе массива projects. При клике на любую из них React Router считает значение id из URL (например, 1, 2 или 3) и передает его в компонент ProjectDetail через хук useParams(), где это значение используется для отображения соответствующей информации о проекте.

Шаг 6. Организуем вложенные маршруты и общий лейаут
Пока наш Navbar живет прямо в файле App.jsx рядом с маршрутами — это работает, но такой подход не получится масштабировать при росте приложения. В реальных проектах разные разделы сайта часто требуют разного лейаута: например, в админ-панели может быть боковое меню, на публичных страницах — минималистичный хедер, а в личном кабинете — уже своя навигация и футер.
Чтобы это исправить, в React Router предусмотрены вложенные маршруты и компонент Outlet. Родительский маршрут отвечает за общий лейаут с элементами, которые присутствуют на всех страницах раздела (например, навигация и футер). А Outlet — это специальный компонент внутри лейаута, который React Router заменяет содержимым активного дочернего маршрута в зависимости от текущего URL. За счет этого навигация и другие общие элементы остаются на месте, а меняется только та часть страницы, которая находится внутри Outlet.
Создайте файл Layout.jsx в папке components — это будет общий компонент, который объединяет навигационную панель Navbar и динамическую область для отображения содержимого дочерних маршрутов через Outlet:
import { Outlet } from 'react-router'
import Navbar from './Navbar'
export default function Layout() {
return (
<>
<header>
<Navbar />
</header>
<main>
<Outlet />
</main>
</>
)
}
Нам осталось перестроить маршруты в App.jsx:
import { Routes, Route } from 'react-router'
import Layout from './components/Layout'
import Home from './pages/Home'
import About from './pages/About'
import Projects from './pages/Projects'
import ProjectDetail from './pages/ProjectDetail'
import NotFound from './pages/NotFound'
export default function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/projects" element={<Projects />} />
<Route path="/projects/:id" element={<ProjectDetail />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
)
}
У родительского Route нет пропа path — только element. Это означает, что он не привязан к конкретному адресу и просто оборачивает дочерние маршруты своим лейаутом. Когда пользователь переходит на /about, React Router рендерит Layout, а внутрь Outlet вставляет About. А вот страница 404 намеренно вынесена за пределы Layout, поскольку у нее нет смысла показывать обычную навигацию.
На этом этапе роутинг готов. У нас есть рабочая навигация, динамические маршруты, вложенные маршруты и страница 404. Этого достаточно для большинства задач, с которыми вы столкнетесь на старте. Однако React Router умеет гораздо больше — у него есть защищенные маршруты, работа с историей браузера, редиректы и многое другое, о чем мы поговрим в другой раз. А на следующем шаге мы просто займемся оформлением, чтобы все напоминало готовый сайт-портфолио.
Шаг 7. Заполняем страницы и наводим красоту
В процессе написания этой статьи у нас в редакторе была установлена тема Tokyo Night — это одна из самых популярных цветовых схем среди разработчиков. У нее довольно приятный контраст, поэтому глаза не так сильно устают при долгой работе. Возьмем палитру этой темы за основу для оформления сайта-портфолио.

Для стилизации мы будем использовать CSS-модули — это встроенная возможность React, при которой каждый компонент получает собственный файл стилей с расширением .module.css (например, Home.module.css). При импорте такого файла React автоматически генерирует уникальные имена классов, добавляя к ним хеш — например, вместо .container в браузере будет .Home_container__a1b2c.
Главное преимущество модулей связано с полной изоляцией стилей: классы работают в пределах компонента и не могут конфликтовать с остальным сайтом. Это позволяет использовать понятные названия вроде .container или .title в любом количестве компонентов и не переживать, что что-то сломается в другом месте.
Также пришло время заменить текстовые заглушки на что-то более осмысленное. Конечно, для реального портфолио тексты нужно прорабатывать более детально, но для учебного проекта про роутинг мы ограничимся описанием навыков и опыта.
А в завершении мы сделаем анимированный футер с двумя ниндзя, которые будут играть в волейбол, перебрасывая друг другу логотип React. Это немного оживит интерфейс и покажет, как можно добавлять нестандартные элементы в верстку.
Чтобы не растягивать статью и не описывать каждый CSS-модуль отдельно — вы можете скачать готовую папку src с полным кодом проекта по этой ссылке и заменить ее у себя в редакторе. После замены перезапустите проект командой npm run dev в терминале и откройте приложение в браузере, чтобы увидеть результат.
Полезное по теме
- React — что это за JS библиотека компонентов и для чего нужна
- Single Page Application — что это такое, как работает SPA
- React Native — что это такое простыми словами, фреймворк JS
- Smarter, not harder: полезные библиотеки и фреймворки JavaScript
- Как программисту создать портфолио, которое покорит работодателей и заказчиков
