React Router предоставляет гибкую архитектуру для построения маршрутизации в React-приложениях. Одним из важных аспектов современной маршрутизации является обработка ошибок, возникающих при динамической загрузке компонентов или модулей. Правильная организация этого процесса позволяет избежать полного «падения» приложения и улучшает пользовательский опыт.
React.Suspense и ленивой загрузкиДля динамического импорта компонентов React Router активно применяет
ленивую загрузку с помощью функции React.lazy. Пример
базовой структуры:
import React, { Suspense, lazy } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
function App() {
return (
<Router>
<Suspense fallback={<div>Загрузка...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
}
Suspense обеспечивает отображение резервного контента
(fallback) до завершения загрузки ленивого компонента. Однако он не
перехватывает ошибки при загрузке модуля, таких как сетевые ошибки или
ошибки синтаксиса в модуле.
ErrorBoundaryДля перехвата ошибок при загрузке ленивых компонентов необходимо
использовать компонент-контейнер ошибок (Error Boundary). Он реализуется
через жизненный цикл componentDidCatch или хук
getDerivedStateFromError.
Пример Error Boundary:
import React from "react";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error("Ошибка при загрузке модуля:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>Произошла ошибка при загрузке страницы</div>;
}
return this.props.children;
}
}
export default ErrorBoundary;
Объединение Suspense и ErrorBoundary
позволяет корректно отображать и состояние загрузки, и ошибки:
<Router>
<ErrorBoundary>
<Suspense fallback={<div>Загрузка...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</ErrorBoundary>
</Router>
Иногда требуется детальнее управлять ошибками загрузки, например,
показать разные сообщения в зависимости от типа ошибки. Для этого можно
обернуть динамический импорт в функцию с обработкой
catch:
const loadComponent = (importFunc) =>
lazy(() =>
importFunc().catch((error) => {
console.error("Ошибка при динамическом импорте:", error);
return { default: () => <div>Не удалось загрузить страницу</div> };
})
);
const Home = loadComponent(() => import("./pages/Home"));
const About = loadComponent(() => import("./pages/About"));
Такой подход позволяет задавать кастомное поведение при ошибках без полного падения приложения.
RoutesReact Router версии 6 и выше предоставляет возможность добавлять
маршруты с собственными обработчиками ошибок через
errorElement:
import ErrorPage from "./pages/ErrorPage";
<Routes>
<Route path="/" element={<Home />} errorElement={<ErrorPage />} />
<Route path="/about" element={<About />} errorElement={<ErrorPage />} />
</Routes>
В этом случае при возникновении ошибки внутри конкретного маршрута вместо общего Error Boundary будет отображаться указанный компонент ошибки. Такой подход упрощает локализацию ошибок для отдельных страниц.
Для крупных приложений важно не только отображать ошибки
пользователю, но и отслеживать их. Комбинация
componentDidCatch или getDerivedStateFromError
с внешними сервисами аналитики позволяет фиксировать статистику
падений:
componentDidCatch(error, errorInfo) {
sendErrorToAnalyticsService({ error, errorInfo });
}
Suspense и
ErrorBoundary, чтобы пользователь видел индикатор
загрузки и корректное сообщение об ошибке.errorElement, если необходимо индивидуальное
поведение.Такой подход обеспечивает безопасную и масштабируемую маршрутизацию, где пользователи всегда видят либо корректный контент, либо информативное сообщение об ошибке, а разработчики получают полные данные для анализа.