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

Обробка помилок та 404: ErrorBoundary і fallback-маршрути

Створення сторінок 404 для неіснуючих маршрутів, обробка помилок рендерингу через ErrorBoundary, catch-all маршрути через wildcard, типізовані компоненти помилок, користувацький UX для помилкових станів.

Обробка помилок та 404: ErrorBoundary і fallback-маршрути

🎯 Мета лекції

  • Створити сторінку 404 для неіснуючих маршрутів через catch-all маршрут.
  • Обробляти помилки рендерингу через React ErrorBoundary.
  • Реалізувати користувацький UX для помилкових станів.
  • Забезпечити типобезпеку компонентів помилок у TypeScript.
  • Інтегрувати логування помилок для моніторингу.

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

  • 404 (Not Found): HTTP-статус, що вказує на відсутність запитуваного ресурсу.
  • ErrorBoundary: React-компонент, що перехоплює помилки рендерингу у дочірніх компонентах.
  • Catch-all Route: маршрут із wildcard (*), що збігається з будь-яким невизначеним шляхом.
  • Fallback UI: запасний інтерфейс, що показується при помилці.

Проблема: неіснуючі маршрути

Користувач може ввести неіснуючий URL вручну або перейти за застарілим посиланням. Без обробки React Router просто не рендерить жодного маршруту — користувач бачить порожню сторінку:

<Routes>
  <Route path="/" element={<HomePage />} />
  <Route path="/about" element={<AboutPage />} />
  <Route path="/contact" element={<ContactPage />} />
  {/* Що відбувається при URL /nonexistent? */}
</Routes>

URL /nonexistent не збігається з жодним маршрутом → порожня сторінка (або лише Layout, якщо він поза <Routes>).


Рішення: Catch-All маршрут для 404

Додайте маршрут із path="*" наприкінці списку маршрутів — він збігається з будь-яким невизначеним шляхом:

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

function NotFoundPage() {
  return (
    <div style={{ textAlign: "center", padding: "2rem" }}>
      <h1>404 — Сторінку не знайдено</h1>
      <p>Сторінка, яку ви шукаєте, не існує.</p>
      <Link to="/">Повернутись на головну</Link>
    </div>
  );
}

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

      {/* Catch-all маршрут — має бути останнім! */}
      <Route path="*" element={<NotFoundPage />} />
    </Routes>
  );
}
Порядок має значення! Маршрут path="*" збігається з будь-яким URL, тому він завжди має бути останнім. Якщо розмістити його на початку, жоден інший маршрут не спрацює.✅ Правильно:* наприкінці.
❌ Неправильно:* на початку або в середині.

Вкладені 404: catch-all у вкладених маршрутах

Catch-all маршрут можна використовувати всередині вкладених маршрутів для обробки невалідних підшляхів:

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

  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="profile" element={<Profile />} />
    <Route path="settings" element={<Settings />} />

    {/* Catch-all для невалідних підшляхів Dashboard */}
    <Route path="*" element={<DashboardNotFound />} />
  </Route>

  {/* Глобальний catch-all */}
  <Route path="*" element={<NotFoundPage />} />
</Routes>

Поведінка:

  • /dashboard/profile → <Profile>
  • /dashboard/invalid → <DashboardNotFound> (вкладений catch-all)
  • /completely-wrong → <NotFoundPage> (глобальний catch-all)
function DashboardNotFound() {
  return (
    <div>
      <h2>Сторінка Dashboard не знайдена</h2>
      <p>Такої підсторінки в Dashboard не існує.</p>
      <Link to="/dashboard">Повернутись до Dashboard</Link>
    </div>
  );
}
Вкладені 404 дозволяють надавати контекстні повідомлення про помилки замість загального "Сторінку не знайдено". Користувач розуміє, що він у правильному розділі, але обрав невалідний підрозділ.

Редирект на 404 через useNavigate

Іноді потрібно програмно перенаправити на 404 — наприклад, коли завантаження даних повертає помилку:

import { useParams, useNavigate } from "react-router";
import { useEffect, useState } from "react";

type Product = {
  id: string;
  name: string;
  price: number;
};

function ProductPage() {
  const { productId } = useParams();
  const navigate = useNavigate();
  const [product, setProduct] = useState<Product | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadProduct() {
      try {
        const response = await fetch(`/api/products/${productId}`);

        if (!response.ok) {
          // Продукт не знайдено — редиректимо на 404
          navigate("/404", { replace: true });
          return;
        }

        const data = await response.json();
        setProduct(data);
      } catch (error) {
        console.error("Помилка завантаження:", error);
        navigate("/404", { replace: true });
      } finally {
        setLoading(false);
      }
    }

    loadProduct();
  }, [productId, navigate]);

  if (loading) {
    return <div>Завантаження...</div>;
  }

  if (!product) {
    return null; // Редирект вже виконано
  }

  return (
    <div>
      <h1>{product.name}</h1>
      <p>Ціна: {product.price} ₴</p>
    </div>
  );
}
Для такого підходу потрібно створити іменований маршрут для 404:
<Route path="/404" element={<NotFoundPage />} />
<Route path="*" element={<NotFoundPage />} />
Різниця:
  • /404 — явна сторінка помилки, на яку можна редиректити.
  • * — catch-all для всіх невизначених URL.
Обидва рендерять той самий компонент, але /404 дозволяє програмно перенаправляти на нього.

ErrorBoundary: обробка помилок рендерингу

ErrorBoundary — це React-компонент, що перехоплює помилки JavaScript у дочірніх компонентах під час рендерингу, у життєвому циклі та в конструкторах (але не в асинхронному коді чи обробниках подій).

React не надає вбудований ErrorBoundary для функціональних компонентів — потрібно використовувати класовий компонент або бібліотеку react-error-boundary.

Встановлення бібліотеки

npm install react-error-boundary

Базове використання

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

function ErrorFallback({ error }: { error: Error }) {
  return (
    <div role="alert" style={{ padding: "2rem", border: "2px solid red" }}>
      <h2>Щось пішло не так</h2>
      <pre style={{ color: "red" }}>{error.message}</pre>
      <button onClick={() => window.location.reload()}>
        Перезавантажити сторінку
      </button>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/broken" element={<BrokenComponent />} />
          <Route path="*" element={<NotFoundPage />} />
        </Routes>
      </ErrorBoundary>
    </BrowserRouter>
  );
}

// Компонент, що навмисно викидає помилку
function BrokenComponent() {
  throw new Error("Цей компонент зламаний!");
  return <div>Ви не побачите цей текст</div>;
}
Параметри ErrorBoundary:
  • FallbackComponent: компонент, що рендериться при помилці. Отримує { error, resetErrorBoundary }.
  • fallback: React-елемент замість компонента (для простих випадків).
  • onError: callback для логування помилок ((error, info) => { ... }).
  • onReset: callback, що викликається при скиданні помилки.
  • resetKeys: масив залежностей — при їх зміні ErrorBoundary автоматично скидається.

Локальні ErrorBoundary для окремих секцій

Замість одного глобального ErrorBoundary можна обгорнути окремі секції:

function App() {
  return (
    <BrowserRouter>
      <Header />

      <ErrorBoundary FallbackComponent={MainContentError}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </ErrorBoundary>

      <ErrorBoundary FallbackComponent={SidebarError}>
        <Sidebar />
      </ErrorBoundary>

      <Footer />
    </BrowserRouter>
  );
}

Якщо помилка виникне у <Dashboard>, зламається лише контент, а <Sidebar>, <Header> та <Footer> продовжать працювати.

Гранульований підхід:
  • Глобальний ErrorBoundary для критичних помилок, що ламають весь застосунок.
  • Локальні ErrorBoundary для ізольованих секцій (сайдбар, модальні вікна, віджети).
Це покращує UX — користувач бачить помилку лише у зламаній частині, решта інтерфейсу залишається функціональною.

Логування помилок до Sentry / LogRocket

Для продакшн-моніторингу інтегруйте ErrorBoundary з сервісами логування:

import * as Sentry from "@sentry/react";

function App() {
  return (
    <BrowserRouter>
      <ErrorBoundary
        FallbackComponent={ErrorFallback}
        onError={(error, errorInfo) => {
          // Логування до Sentry
          Sentry.captureException(error, {
            contexts: {
              react: {
                componentStack: errorInfo.componentStack,
              },
            },
          });

          // Або власний сервіс
          fetch("/api/log-error", {
            method: "POST",
            body: JSON.stringify({ error: error.message, stack: error.stack }),
          });
        }}
      >
        <Routes>{/* Маршрути */}</Routes>
      </ErrorBoundary>
    </BrowserRouter>
  );
}

Типізація компонентів помилок

Для TypeScript створіть типізований fallback-компонент:

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

function TypedErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
  return (
    <div role="alert">
      <h2>Виникла помилка</h2>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Спробувати знову</button>
    </div>
  );
}

// Використання
<ErrorBoundary FallbackComponent={TypedErrorFallback}>
  {/* Компоненти */}
</ErrorBoundary>

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


Підсумок

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

  • Створення сторінок 404 через catch-all маршрут path="*".
  • Вкладені 404 для контекстних повідомлень про помилки.
  • Обробку помилок рендерингу через ErrorBoundary.
  • Локальні ErrorBoundary для ізоляції помилок у секціях.
  • Програмний редирект на 404 при помилках завантаження даних.
  • Інтеграцію логування помилок з моніторинговими сервісами.

🎯 Ключові практики

  • Завжди додавайте catch-all маршрут наприкінці списку маршрутів.
  • Використовуйте ErrorBoundary для критичних секцій застосунку.
  • Логуйте помилки до Sentry / LogRocket у продакшні.
  • Надавайте зрозумілі повідомлення про помилки з можливістю повернутись.
  • Тестуйте обробку помилок — спеціально викидайте помилки у розробці.
Copyright © 2026