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

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

Повний довідник альтернативного об'єктного синтаксису React Router v7 через createBrowserRouter: від базових маршрутів до складних вкладених структур, динамічних параметрів, loaders, actions та обробки помилок.

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

🎯 Мета лекції

  • Опанувати Data Router API як альтернативу JSX-синтаксису.
  • Навчитися визначати маршрути через масив об'єктів.
  • Зрозуміти переваги централізованої конфігурації маршрутів.
  • Побачити еквіваленти всіх патернів у об'єктному синтаксисі.

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

  • createBrowserRouter: функція для створення router-інстансу з масиву об'єктів маршрутів.
  • RouterProvider: компонент для рендерингу router-інстансу замість <BrowserRouter>.
  • Route Object: об'єкт конфігурації маршруту з полями path, element, children, тощо.
  • Data Router API: підхід до визначення маршрутів, що підтримує loaders, actions та errorElement.

Два способи визначення маршрутів

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

1. JSX-синтаксис (Declarative Routing)

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

<BrowserRouter>
  <Routes>
    <Route path="/" element={<HomePage />} />
    <Route path="/about" element={<AboutPage />} />
  </Routes>
</BrowserRouter>

Переваги:

  • Візуально наочний
  • Природний для React-розробників
  • Компоненти можна розподіляти по файлах

2. Data Router API (Object-based Routing)

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

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

<RouterProvider router={router} />

Переваги:

  • Централізована конфігурація
  • Підтримка data loaders та actions
  • Typed Routes через TypeScript
  • Легше для програмної маніпуляції маршрутами
Обидва підходи створюють ідентичну функціональність. Вибір залежить від ваших потреб:
  • Простий SPA без data fetching → обидва підходи рівноцінні
  • Складний застосунок із loaders/actions → Data Router API краще
  • Поступова міграція з v6 → JSX-синтаксис (сумісний з v6)

Базові маршрути

JSX-синтаксис

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

function Navigation() {
  return (
    <nav>
      <Link to="/">Головна</Link>
      <Link to="/about">Про нас</Link>
      <Link to="/contact">Контакти</Link>
    </nav>
  );
}

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

Data Router API (еквівалент)

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

// Layout для навігації
function RootLayout() {
  return (
    <>
      <nav>
        <Link to="/">Головна</Link>
        <Link to="/about">Про нас</Link>
        <Link to="/contact">Контакти</Link>
      </nav>
      <Outlet /> {/* Тут рендериться активний маршрут */}
    </>
  );
}

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "about", element: <AboutPage /> },
      { path: "contact", element: <ContactPage /> },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

Вкладені маршрути та Outlet

JSX-синтаксис

<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="profile" element={<ProfilePage />} />
    <Route path="settings" element={<SettingsPage />} />
  </Route>
</Routes>

Data Router API (еквівалент)

const router = createBrowserRouter([
  {
    path: "/dashboard",
    element: <DashboardLayout />,
    children: [
      { index: true, element: <DashboardHome /> },
      { path: "profile", element: <ProfilePage /> },
      { path: "settings", element: <SettingsPage /> },
    ],
  },
]);

Створені URL:

  • /dashboard → DashboardLayout + DashboardHome
  • /dashboard/profile → DashboardLayout + ProfilePage
  • /dashboard/settings → DashboardLayout + SettingsPage

Layout Routes (без path)

JSX-синтаксис

<Routes>
  <Route element={<PublicLayout />}>
    <Route path="/" element={<HomePage />} />
    <Route path="/about" element={<AboutPage />} />
  </Route>

  <Route element={<PrivateLayout />}>
    <Route path="/dashboard" element={<Dashboard />} />
    <Route path="/profile" element={<Profile />} />
  </Route>
</Routes>

Data Router API (еквівалент)

const router = createBrowserRouter([
  {
    element: <PublicLayout />,
    children: [
      { path: "/", element: <HomePage /> },
      { path: "/about", element: <AboutPage /> },
    ],
  },
  {
    element: <PrivateLayout />,
    children: [
      { path: "/dashboard", element: <Dashboard /> },
      { path: "/profile", element: <Profile /> },
    ],
  },
]);

Динамічні параметри

JSX-синтаксис

<Routes>
  <Route path="/users/:userId" element={<UserProfile />} />
  <Route path="/posts/:postId" element={<PostDetails />} />
  <Route path="/categories/:categoryId/products/:productId" element={<Product />} />
</Routes>

Data Router API (еквівалент)

const router = createBrowserRouter([
  { path: "/users/:userId", element: <UserProfile /> },
  { path: "/posts/:postId", element: <PostDetails />} ,
  { path: "/categories/:categoryId/products/:productId", element: <Product /> },
]);

Витягування параметрів ідентичне в обох підходах через useParams():

import { useParams } from "react-router";

function UserProfile() {
  const { userId } = useParams();
  return <div>Користувач: {userId}</div>;
}

Необов'язкові сегменти та Wildcard

JSX-синтаксис

<Routes>
  <Route path="/:lang?/categories" element={<Categories />} />
  <Route path="/docs/*" element={<Documentation />} />
  <Route path="*" element={<NotFoundPage />} />
</Routes>

Data Router API (еквівалент)

const router = createBrowserRouter([
  { path: "/:lang?/categories", element: <Categories /> },
  { path: "/docs/*", element: <Documentation /> },
  { path: "*", element: <NotFoundPage /> },
]);

Обробка помилок: ErrorBoundary

JSX-синтаксис + react-error-boundary

import { ErrorBoundary } from "react-error-boundary";

<BrowserRouter>
  <ErrorBoundary FallbackComponent={ErrorFallback}>
    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path="/broken" element={<BrokenComponent />} />
    </Routes>
  </ErrorBoundary>
</BrowserRouter>

Data Router API (вбудована підтримка)

function ErrorFallback() {
  return (
    <div>
      <h2>Щось пішло не так</h2>
      <button onClick={() => window.location.reload()}>
        Перезавантажити
      </button>
    </div>
  );
}

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorFallback />, // Обробка помилок на рівні маршруту!
    children: [
      { index: true, element: <HomePage /> },
      { path: "broken", element: <BrokenComponent /> },
    ],
  },
]);
Перевага Data Router API:errorElement працює на рівні конкретного маршруту, що дозволяє мати різні fallback-компоненти для різних секцій застосунку без додаткових бібліотек.

Глибока вкладеність: три рівні

JSX-синтаксис

<Routes>
  <Route path="/projects" element={<ProjectsLayout />}>
    <Route index element={<ProjectsList />} />
    <Route path=":projectId" element={<ProjectLayout />}>
      <Route index element={<ProjectOverview />} />
      <Route path="tasks" element={<TasksList />} />
      <Route path="team" element={<TeamMembers />} />
    </Route>
  </Route>
</Routes>

Data Router API (еквівалент)

const router = createBrowserRouter([
  {
    path: "/projects",
    element: <ProjectsLayout />,
    children: [
      { index: true, element: <ProjectsList /> },
      {
        path: ":projectId",
        element: <ProjectLayout />,
        children: [
          { index: true, element: <ProjectOverview /> },
          { path: "tasks", element: <TasksList /> },
          { path: "team", element: <TeamMembers /> },
        ],
      },
    ],
  },
]);

Створені URL:

  • /projects → ProjectsList
  • /projects/abc-123 → ProjectOverview
  • /projects/abc-123/tasks → TasksList
  • /projects/abc-123/team → TeamMembers

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

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

// src/router/index.tsx
import { createBrowserRouter } from "react-router";
import { publicRoutes } from "./publicRoutes";
import { dashboardRoutes } from "./dashboardRoutes";
import { adminRoutes } from "./adminRoutes";

export const router = createBrowserRouter([
  ...publicRoutes,
  ...dashboardRoutes,
  ...adminRoutes,
]);
// src/router/publicRoutes.tsx
import { RouteObject } from "react-router";
import RootLayout from "@/layouts/RootLayout";
import HomePage from "@/pages/HomePage";
import AboutPage from "@/pages/AboutPage";

export const publicRoutes: RouteObject[] = [
  {
    path: "/",
    element: <RootLayout />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "about", element: <AboutPage /> },
    ],
  },
];
// src/router/dashboardRoutes.tsx
import { RouteObject } from "react-router";
import DashboardLayout from "@/layouts/DashboardLayout";
import DashboardHome from "@/pages/Dashboard/DashboardHome";
import ProfilePage from "@/pages/Dashboard/ProfilePage";

export const dashboardRoutes: RouteObject[] = [
  {
    path: "/dashboard",
    element: <DashboardLayout />,
    children: [
      { index: true, element: <DashboardHome /> },
      { path: "profile", element: <ProfilePage /> },
    ],
  },
];
Тип RouteObject забезпечує типобезпеку TypeScript для об'єктів маршрутів. Використовуйте його для всіх експортованих конфігурацій маршрутів.

Типізація маршрутів через TypeScript

Data Router API чудово працює з TypeScript:

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

// Типізація параметрів маршруту
type UserProfileParams = {
  userId: string;
};

// Типізація об'єкта маршруту
const userRoutes: RouteObject[] = [
  {
    path: "/users/:userId",
    element: <UserProfile />,
  },
];

// Використання у компоненті з типізацією
function UserProfile() {
  const { userId } = useParams<UserProfileParams>();
  
  if (!userId) {
    return <div>Помилка: ID не вказано</div>;
  }

  return <div>Профіль користувача {userId}</div>;
}

Порівняльна таблиця

ФункціяJSX-синтаксисData Router API
Базовий маршрут<Route path="/" element={<Home />} />{ path: "/", element: <Home /> }
Вкладені маршрути<Route> всередині <Route>children: [...] у об'єкті
Index маршрут<Route index element={...} />{ index: true, element: ... }
Layout Route<Route element={<Layout />}>{ element: <Layout />, children: [...] }
Динамічні параметриpath="/users/:id"path: "/users/:id"
Wildcardpath="*"path: "*"
ErrorBoundaryЗовнішня бібліотекаerrorElement: <Fallback /> (вбудовано)
Data LoadersНе підтримуєтьсяloader: async () => {...}
Form ActionsНе підтримуєтьсяaction: async () => {...}

Коли використовувати кожен підхід?

Використовуйте JSX-синтаксис (<Routes>), якщо:

  • Мігруєте з React Router v6 і хочете мінімальних змін
  • Проєкт простий, без data fetching на рівні маршрутів
  • Команда звикла до декларативного JSX-стилю
  • Маршрути розподілені по різних файлах/компонентах

Використовуйте Data Router API (createBrowserRouter), якщо:

  • Потрібні data loaders та actions
  • Хочете централізовану конфігурацію маршрутів
  • Потрібна вбудована обробка помилок через errorElement
  • Плануєте програмно маніпулювати маршрутами
  • Потрібна максимальна типобезпека TypeScript
У наступних версіях React Router Data Router API стане рекомендованим підходом, оскільки він підтримує всі нові можливості (loaders, actions, деферінг, revalidation). JSX-синтаксис залишається для зворотної сумісності.

Підсумок

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

  • Два способи визначення маршрутів: JSX та Data Router API.
  • Створення базових та вкладених маршрутів через об'єкти.
  • Переваги централізованої конфігурації через createBrowserRouter.
  • Вбудовану обробку помилок через errorElement.
  • Структурування маршрутів для великих проєктів.
  • Типізацію маршрутів через TypeScript.

🎯 Ключові переваги Data Router API

  • Централізація: всі маршрути в одному місці.
  • Типобезпека: повна підтримка TypeScript.
  • Розширюваність: loaders, actions, errorElement.
  • Програмна маніпуляція: легко генерувати маршрути динамічно.
  • Майбутнє: всі нові функції додаватимуться саме сюди.
Copyright © 2026