React Router v7: Маршрутизація в SPA

Встановлення та базова конфігурація React Router v7

Встановлення React Router v7 як бібліотеки для SPA-маршрутизації, налаштування BrowserRouter, перші маршрути через Routes та Route, розуміння декларативного підходу до навігації.

Встановлення та базова конфігурація React Router v7

🔗 Офіційний референс: React Router v7 Installation (Library Mode)

🎯 Мета лекції

  • Встановити React Router v7 як бібліотеку для існуючого React-проєкту.
  • Зрозуміти різницю між режимом бібліотеки та фреймворку React Router.
  • Налаштувати базову маршрутизацію через компоненти <BrowserRouter>, <Routes> та <Route>.
  • Створити перший SPA-застосунок із кількома сторінками без перезавантаження.

🔑 Ключові терміни

  • React Router (Library Mode): бібліотека клієнтської маршрутизації для React без серверних можливостей.
  • BrowserRouter: компонент, що використовує History API браузера для синхронізації UI з URL.
  • Routes: контейнер, що визначає набір можливих маршрутів застосунку.
  • Route: декларативне відображення URL-шляху на React-компонент.

React Router як бібліотека vs фреймворк

React Router v7 підтримує два режими роботи:

Режим фреймворку (Framework Mode)

Повноцінний full-stack фреймворк із:

  • Серверним рендерингом (SSR)
  • Завантаженням даних на сервері (Server Loaders)
  • Серверними діями (Server Actions)
  • Автоматичним code-splitting
  • Вбудованою збіркою та деплоєм

Цей режим конкурує з Next.js та Remix (насправді, React Router v7 — це еволюція Remix).

Режим бібліотеки (Library Mode) — наш фокус

Класична клієнтська бібліотека маршрутизації для існуючих React-застосунків:

  • Працює виключно на клієнті (без SSR)
  • Інтегрується з будь-яким збирачем (Vite, Webpack, Parcel)
  • Не нав'язує структуру проєкту
  • Сумісна з будь-яким бекендом (REST API, GraphQL, WebSocket)
У цьому курсі ми вивчаємо React Router виключно як бібліотеку, оскільки це найпоширеніший сценарій використання: клієнтський SPA-застосунок, що взаємодіє з незалежним бекенд-API. Режим фреймворку виходить за межі курсу.

Встановлення

React Router v7 поставляється як єдиний пакет react-router (раніше це був react-router-dom для веб-застосунків). Для веб-проєктів зараз рекомендується встановлювати саме react-router, який автоматично включає всі необхідні веб-API.

npm install react-router
Якщо ви оновлюєте проєкт з React Router v6, пакет react-router-dom все ще працює і є псевдонімом для react-router. Проте для нових проєктів рекомендується використовувати просто react-router.

Інтеграція з Vite-проєктом

Припустимо, у нас є стандартний Vite + React + TypeScript проєкт:

npm create vite@latest my-spa -- --template react-ts
cd my-spa
npm install
npm install react-router

Структура проєкту:


Перший крок: обгортання у BrowserRouter

BrowserRouter — це кореневий компонент React Router, що використовує History API браузера (методи pushState, replaceState, подія popstate) для синхронізації UI з URL без перезавантаження сторінки.

// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
Чому саме у main.tsx, а не у App.tsx?BrowserRouter створює контекст маршрутизації, доступний для всіх дочірніх компонентів через React Context. Розміщення на найвищому рівні гарантує, що весь застосунок має доступ до хуків маршрутизації (useNavigate, useParams, useLocation тощо).
Частая помилка: обгортання <App> у <BrowserRouter> всередині самого компонента App. Це призведе до того, що хуки маршрутизації не працюватимуть поза компонентом App.✅ Правильно:<BrowserRouter> у main.tsx на найвищому рівні.
❌ Неправильно:<BrowserRouter> всередині App.tsx.

Другий крок: визначення маршрутів

Тепер визначимо базові маршрути через компоненти <Routes> та <Route>.

// src/App.tsx
import { Routes, Route } from "react-router";

function HomePage() {
  return (
    <div>
      <h1>Головна сторінка</h1>
      <p>Ласкаво просимо до нашого застосунку!</p>
    </div>
  );
}

function AboutPage() {
  return (
    <div>
      <h1>Про нас</h1>
      <p>Ми команда розробників, що створюють чудові SPA.</p>
    </div>
  );
}

function ContactPage() {
  return (
    <div>
      <h1>Контакти</h1>
      <p>Email: contact@example.com</p>
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path="/about" element={<AboutPage />} />
      <Route path="/contact" element={<ContactPage />} />
    </Routes>
  );
}

export default App;
Анатомія <Route>:
  • path — URL-шлях, що відповідає цьому маршруту (наприклад, /, /about, /users/:id).
  • element — React-компонент, що рендериться, коли URL збігається з path.
React Router автоматично вибирає найбільш відповідний маршрут на основі поточного URL. Якщо URL /about, рендериться <AboutPage />.

Щоб переходити між сторінками без перезавантаження, використовуємо компонент <Link>:

// src/App.tsx
import { Routes, Route, Link } from "react-router";

function Navigation() {
  return (
    <nav style={{ padding: "1rem", borderBottom: "1px solid #ccc" }}>
      <Link to="/" style={{ marginRight: "1rem" }}>
        Головна
      </Link>
      <Link to="/about" style={{ marginRight: "1rem" }}>
        Про нас
      </Link>
      <Link to="/contact">Контакти</Link>
    </nav>
  );
}

function HomePage() {
  return (
    <div>
      <h1>Головна сторінка</h1>
      <p>Ласкаво просимо до нашого застосунку!</p>
    </div>
  );
}

function AboutPage() {
  return (
    <div>
      <h1>Про нас</h1>
      <p>Ми команда розробників, що створюють чудові SPA.</p>
    </div>
  );
}

function ContactPage() {
  return (
    <div>
      <h1>Контакти</h1>
      <p>Email: contact@example.com</p>
    </div>
  );
}

function App() {
  return (
    <>
      <Navigation />
      <main style={{ padding: "1rem" }}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/about" element={<AboutPage />} />
          <Route path="/contact" element={<ContactPage />} />
        </Routes>
      </main>
    </>
  );
}

export default App;
Різниця між <Link> та <a>:
  • <a href="/about"> — викликає повне перезавантаження сторінки, втрачається весь стан React, відбувається новий запит до сервера.
  • <Link to="/about"> — клієнтська навігація: React Router перехоплює клік, змінює URL через History API, React перерендерює компонент без перезавантаження сторінки.
Завжди використовуйте <Link> для внутрішньої навігації у SPA.

Запуск та тестування

npm run dev

Відкрийте браузер за адресою http://localhost:5173. Натискайте на посилання навігації — сторінка не перезавантажується, URL змінюється миттєво, контент оновлюється плавно.

npm run dev
$ npm run dev
VITE v6.0.0 ready in 324 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
Відкрийте DevTools браузера і перейдіть на вкладку Network. При кліку на посилання <Link> ви не побачите нових запитів до сервера за HTML-документами — вся навігація відбувається на клієнті через JavaScript.

Альтернативний синтаксис: Data Router API через createBrowserRouter

React Router v7 пропонує два способи визначення маршрутів:

  1. JSX-синтаксис (який ми щойно розглянули) — через компоненти <Routes> та <Route>
  2. Об'єктний синтаксис — через функцію createBrowserRouter та масив об'єктів маршрутів

Другий підхід називається Data Router API і надає додаткові можливості:

Переваги Data Router API

  • Data Loaders — завантаження даних до рендерингу компонента
  • Actions — обробка форм на рівні маршруту
  • Error Boundaries — обробка помилок на рівні маршруту
  • Lazy Loading — динамічне завантаження компонентів
  • Централізована конфігурація — всі маршрути в одному місці

Базовий приклад з createBrowserRouter

// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router";
import "./index.css";

// Компоненти сторінок
function HomePage() {
  return (
    <div>
      <h1>Головна сторінка</h1>
      <p>Ласкаво просимо до нашого застосунку!</p>
    </div>
  );
}

function AboutPage() {
  return (
    <div>
      <h1>Про нас</h1>
      <p>Ми команда розробників, що створюють чудові SPA.</p>
    </div>
  );
}

function ContactPage() {
  return (
    <div>
      <h1>Контакти</h1>
      <p>Email: contact@example.com</p>
    </div>
  );
}

// Визначення маршрутів через масив об'єктів
const router = createBrowserRouter([
  {
    path: "/",
    element: <HomePage />,
  },
  {
    path: "/about",
    element: <AboutPage />,
  },
  {
    path: "/contact",
    element: <ContactPage />,
  },
]);

// Рендеринг застосунку
ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);
Ключові відмінності:
JSX-синтаксис (<Routes>)Data Router API (createBrowserRouter)
<BrowserRouter> + <Routes>createBrowserRouter() + <RouterProvider>
Маршрути визначаються в JSXМаршрути визначаються в масиві об'єктів
Навігація та базовий роутингДодатково: loaders, actions, errorElement
Маршрути можуть бути розподілені по файлахЦентралізована конфігурація
Який обрати?
  • Для простих SPA без завантаження даних — обидва підходи рівноцінні.
  • Для складних застосунків із data fetching, формами, обробкою помилок — Data Router API краще.

Додавання Layout та навігації

// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider, Link, Outlet } from "react-router";
import "./index.css";

// Layout компонент із навігацією
function RootLayout() {
  return (
    <>
      <nav style={{ padding: "1rem", borderBottom: "1px solid #ccc" }}>
        <Link to="/" style={{ marginRight: "1rem" }}>
          Головна
        </Link>
        <Link to="/about" style={{ marginRight: "1rem" }}>
          Про нас
        </Link>
        <Link to="/contact">Контакти</Link>
      </nav>
      <main style={{ padding: "1rem" }}>
        {/* Тут рендериться активний маршрут */}
        <Outlet />
      </main>
    </>
  );
}

function HomePage() {
  return (
    <div>
      <h1>Головна сторінка</h1>
      <p>Ласкаво просимо до нашого застосунку!</p>
    </div>
  );
}

function AboutPage() {
  return (
    <div>
      <h1>Про нас</h1>
      <p>Ми команда розробників, що створюють чудові SPA.</p>
    </div>
  );
}

function ContactPage() {
  return (
    <div>
      <h1>Контакти</h1>
      <p>Email: contact@example.com</p>
    </div>
  );
}

// Маршрути з Layout
const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      {
        index: true, // Еквівалент path: ""
        element: <HomePage />,
      },
      {
        path: "about",
        element: <AboutPage />,
      },
      {
        path: "contact",
        element: <ContactPage />,
      },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);
Властивість index: true еквівалентна <Route index element={...} /> у JSX-синтаксисі. Це маршрут за замовчуванням, що рендериться за базового URL батьківського маршруту.

Винесення конфігурації у окремий файл

Для великих застосунків рекомендується винести конфігурацію маршрутів у окремий файл:

// src/router.tsx
import { createBrowserRouter } from "react-router";
import RootLayout from "./layouts/RootLayout";
import HomePage from "./pages/HomePage";
import AboutPage from "./pages/AboutPage";
import ContactPage from "./pages/ContactPage";

export const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      {
        index: true,
        element: <HomePage />,
      },
      {
        path: "about",
        element: <AboutPage />,
      },
      {
        path: "contact",
        element: <ContactPage />,
      },
    ],
  },
]);
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router";
import { router } from "./router";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);
Який синтаксис обрати для курсу?У наступних лекціях ми будемо показувати обидва підходи: спочатку JSX-синтаксис (оскільки він візуально наочніший для навчання), а потім еквівалент через createBrowserRouter.Це допоможе вам:
  • Зрозуміти концепції незалежно від синтаксису
  • Вміти читати код в обох стилях
  • Обрати підхід, що краще підходить для вашого проєкту

Альтернатива BrowserRouter: HashRouter

Якщо ваш проєкт розгортається на статичному хостингу (GitHub Pages, Netlify) без налаштування серверних редиректів, можна використовувати HashRouter.

JSX-синтаксис

import { HashRouter } from "react-router";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <HashRouter>
      <App />
    </HashRouter>
  </React.StrictMode>
);

Data Router API

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

const router = createHashRouter([
  {
    path: "/",
    element: <HomePage />,
  },
  {
    path: "/about",
    element: <AboutPage />,
  },
]);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

HashRouter використовує hash-частину URL (після #):

  • https://example.com/#/about
  • https://example.com/#/users/123
Відмінності:
BrowserRouterHashRouter
/about, /users/123/#/about, /#/users/123
Потребує серверної конфігурації для SPAПрацює без серверної конфігурації
Чистіші URL, краще для SEOURL містять #, гірше для SEO
Рекомендація: використовуйте BrowserRouter завжди, коли це можливо. HashRouter — лише для обмежених середовищ без доступу до серверної конфігурації.

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


Підсумок

✅ Що ми опанували

  • Встановили React Router v7 у режимі бібліотеки через npm install react-router.
  • Обгорнули застосунок у <BrowserRouter> для підключення History API.
  • Визначили маршрути через декларативні компоненти <Routes> та <Route>.
  • Реалізували навігацію через компонент <Link> без перезавантаження сторінки.
  • Зрозуміли різницю між BrowserRouter (чисті URL) та HashRouter (hash-based).

🎯 Наступні кроки

У наступних лекціях ми розглянемо:

  • Вкладені маршрути та <Outlet> для складних макетів.
  • Динамічні сегменти URL (/users/:id) та useParams.
  • Активні посилання через <NavLink>.
  • Програмна навігація через useNavigate.
  • Обробка 404 та помилок.
Copyright © 2026