404 в React и SPA: правильный статус и роутинг
SPA на React показывает «Страница не найдена» на клиенте, а сервер на любой путь отвечает index.html с кодом 200. Для поисковых систем это soft 404. Исправляют это на стороне сервера (404 для неизвестных путей), через SSR или предрендеринг, либо добавляют noindex на странице ошибки.
Обновлено 11 октября 2026 г.
Почему SPA отдаёт 200 вместо 404
В одностраничном приложении все адреса обслуживает один файл index.html. Пользователь открывает /products/123, сервер не знает такого файла и по правилу «на всё отдавать index.html» возвращает его с кодом 200. Роутер в браузере сопоставляет путь с маршрутами и, не найдя, рисует компонент «Страница не найдена». Робот видит заголовок 200 и обычную страницу — отсюда soft 404.
location / {
try_files $uri /index.html; # любой неизвестный путь -> 200
}Клиентский роутинг: страница 404 в React Router
В React Router (v6.4+) добавьте маршрут-перехватчик * и errorElement для ошибок загрузки данных:
import { createBrowserRouter, RouterProvider, useRouteError, isRouteErrorResponse } from "react-router-dom";
function NotFound() {
return (
<main>
<meta name="robots" content="noindex" />
<h1>Страница не найдена</h1>
<a href="/">На главную</a>
</main>
);
}
function RouteError() {
const error = useRouteError();
if (isRouteErrorResponse(error) && error.status === 404) return <NotFound />;
return <h1>Что-то пошло не так</h1>;
}
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
errorElement: <RouteError />,
children: [
{ index: true, element: <Home /> },
{
path: "products/:id",
loader: async ({ params }) => {
const res = await fetch("/api/products/" + params.id);
if (res.status === 404) throw new Response("Not Found", { status: 404 });
return res.json();
},
element: <Product />,
},
{ path: "*", element: <NotFound /> },
],
},
]);Тег <meta> внутри компонента в React 19 переносится в <head> автоматически; в более ранних версиях используйте react-helmet-async. Этот приём в документации Google по JavaScript SEO описан как один из способов пометить страницу ошибки в SPA: добавить noindex через JavaScript.
Как отдавать настоящий 404
- Серверная проверка пути. Сервер или API знает, какие адреса существуют (например, карточка товара есть в базе), и для остальных отдаёт 404, а не
index.html. Для статического набора маршрутов можно собрать белый список путей при сборке. - SSR или предрендеринг. Next.js, Remix / React Router framework mode, Vite SSR и подобные решения рендерят страницу на сервере и выставляют статус в ответе. В React Router при SSR статус берётся из
context.statusCodeпослеcreateStaticHandler. - Перенаправление на адрес с настоящим 404. Google допускает перенаправить робота JavaScript-ом на URL, который на сервере отвечает 404 (например,
/not-found). - `noindex` через JavaScript. Если ни одно решение недоступно, добавьте
<meta name="robots" content="noindex">на страницу ошибки. Статус останется 200, но страница не попадёт в индекс.
Если сайту важна индексация, SPA без SSR — слабое место, поскольку статус и содержимое определяются уже после загрузки скриптов. Для разделов, которые должны быть в поиске, выбирайте SSR или статическую генерацию. Про Next.js — в отдельном руководстве.
Особый случай: GitHub Pages и Netlify
GitHub Pages отдаёт кастомную страницу ошибки из файла 404.html в корне публикуемой папки — со статусом 404. Известный приём для SPA — скопировать index.html в 404.html, чтобы приложение открывалось на глубоких ссылках, но тогда каждый внутренний адрес отвечает 404 и плохо индексируется. Netlify и Vercel позволяют задать rewrite на index.html со статусом 200 — это снова soft 404 для неизвестных путей; используйте его только для известных маршрутов.
Редиректы в SPA
<Navigate to="/new" replace /> и navigate() работают только на клиенте. Для поисковых систем это не 301: сервер сначала отдаёт 200. Постоянные редиректы нужны на уровне сервера или платформы хостинга.
/old-page /new-page 301
/blog/* /articles/:splat 301{
"redirects": [
{ "source": "/old-page", "destination": "/new-page", "permanent": true }
]
}Для своего сервера см. nginx и Apache. Правила можно сгенерировать в генераторе редиректов.
Как найти 404 в SPA
Серверные логи не помогут: все запросы к страницам заканчиваются кодом 200. Проверьте, что отдаёт неизвестный адрес, в проверке HTTP-статуса и найдите битые ссылки на странице: проверка битых ссылок. Страницы, которые SPA считает несуществующими, можно фиксировать на клиенте — для этого подходит Monitor404.
Подключение Monitor404 к React
Простой вариант — загружать скрипт при показе компонента 404, чтобы событие фиксировалось на нужном адресе:
import { useEffect } from "react";
function NotFound() {
useEffect(() => {
const s = document.createElement("script");
s.src = "https://collect.monitor404.ru/collect/ВАШ_ТОКЕН/script.js";
s.async = true;
document.body.appendChild(s);
return () => { s.remove(); };
}, []);
return <h1>Страница не найдена</h1>;
}Второй вариант — режим auto: <script src="https://collect.monitor404.ru/collect/ВАШ_ТОКЕН/script.js?mode=auto" async></script> в index.html. Он отправляет событие, только когда сервер ответил на загрузку страницы кодом 404 или 410, поэтому подходит, если сервер уже отдаёт настоящий 404 для неизвестных адресов (SSR, пререндер, настроенный nginx). Для чистой SPA, где сервер всегда отвечает 200, используйте первый вариант. Если у приложения есть бэкенд, 404 можно отправлять и через серверный API с HMAC-подписью — там известен реальный статус.
Частые вопросы
Почему React отдаёт 200 на несуществующую страницу?
Сервер не знает клиентских маршрутов и на любой путь отдаёт index.html. Решение 404 принимает роутер уже в браузере, поэтому HTTP-статус остаётся 200. Нужны проверка путей на сервере, SSR или noindex.
Как сделать страницу 404 в React Router?
Добавьте в конец списка маршрутов { path: "*", element: <NotFound /> } и задайте errorElement для ошибок загрузчиков. В компоненте NotFound добавьте <meta name="robots" content="noindex">.
Как сделать 301 редирект в SPA?
Только на сервере или хостинге: в nginx через return 301, в Netlify через _redirects, в Vercel через redirects с permanent: true. Клиентский navigate() не является 301.
Как сделать страницу 404 на GitHub Pages?
Положите файл 404.html в корень публикуемой папки. GitHub Pages отдаст его для несуществующих адресов с кодом 404.