Урок 09

Обработка 404 и ошибок

404 — несуществующий маршрут

<Routes>
    <Route path="/" element={<Home />} />
    <Route path="/posts" element={<Posts />} />
    <Route path="*" element={<NotFound />} />
</Routes>

* ловит всё, что не совпало с другими маршрутами.

Компонент 404

function NotFound() {
    return (
        <div className="not-found">
            <h1>404</h1>
            <p>Страница не найдена</p>
            <Link to="/">На главную</Link>
        </div>
    );
}

404 внутри раздела

<Route path="/posts" element={<PostsLayout />}>
    <Route index element={<PostsList />} />
    <Route path=":id" element={<PostDetail />} />
    <Route path="*" element={<NotFound />} />
</Route>

URL /posts/unknown/path попадёт в NotFound внутри PostsLayout — с общим сайдбаром.

Ошибки в загрузке данных

function PostDetail() {
    const { id } = useParams();
    const { data, isLoading, error } = useQuery({
        queryKey: ['post', id],
        queryFn: () => fetchPost(id!),
    });

    if (isLoading) return <Spinner />;
    if (error) return <ErrorState error={error} />;
    if (!data) return <NotFound />;

    return <Post post={data} />;
}

ErrorBoundary

React-компонент, который ловит ошибки рендера:

import { Component, ReactNode } from 'react';

class ErrorBoundary extends Component<
    { children: ReactNode; fallback: ReactNode },
    { hasError: boolean }
> {
    state = { hasError: false };

    static getDerivedStateFromError() {
        return { hasError: true };
    }

    componentDidCatch(error: Error, info: React.ErrorInfo) {
        console.error(error, info);
    }

    render() {
        if (this.state.hasError) return this.props.fallback;
        return this.props.children;
    }
}

Использование:

<ErrorBoundary fallback={<ErrorPage />}>
    <App />
</ErrorBoundary>

React Router errorElement

В data router (v6.4+) можно объявить errorElement:

const router = createBrowserRouter([
    {
        path: '/',
        element: <Root />,
        errorElement: <ErrorPage />,
        children: [
            { path: 'posts', element: <Posts /> },
        ],
    },
]);

Если в маршруте выбросится ошибка — покажется ErrorPage.

useRouteError

import { useRouteError, isRouteErrorResponse } from 'react-router-dom';

function ErrorPage() {
    const error = useRouteError();

    if (isRouteErrorResponse(error)) {
        return (
            <>
                <h1>{error.status}</h1>
                <p>{error.statusText}</p>
            </>
        );
    }

    return <h1>Что-то пошло не так</h1>;
}

Логирование

Ошибки нужно логировать на сервер:

componentDidCatch(error: Error, info: React.ErrorInfo) {
    fetch('/api/log', {
        method: 'POST',
        body: JSON.stringify({ message: error.message, stack: info.componentStack }),
    });
}

В продакшене — Sentry, LogRocket, Rollbar.

Стратегия

  • 404 — для несуществующих путей
  • 403 — для доступа без прав
  • 500 — ошибка сервера
  • ErrorBoundary — ошибки рендера
  • Логирование — обязательно

Итоги

  • path="*" для 404
  • ErrorBoundary для ошибок рендера
  • errorElement в data router
  • Разные страницы для 404 и 500
  • Логирование ошибок