404 и редиректы в Next.js
В App Router страницу 404 задаёт файл app/not-found.tsx, а функция notFound() вызывает её из серверного кода. Редиректы настраивают в redirects() файла next.config.js (permanent: true даёт статус 308) или в middleware.
Обновлено 11 октября 2026 г.
Страница 404 в App Router
Создайте app/not-found.tsx. Next.js покажет его для любого адреса, которому не соответствует маршрут, и для случаев, когда вы вызвали notFound(). Страница отдаётся со статусом 404.
import Link from "next/link";
export default function NotFound() {
return (
<main>
<h1>Страница не найдена</h1>
<p>Возможно, адрес изменился. Вернитесь на главную или воспользуйтесь поиском.</p>
<Link href="/">На главную</Link>
</main>
);
}Файл not-found.tsx можно положить и во вложенную папку маршрута — он будет действовать внутри этого сегмента.
notFound() для динамических страниц
import { notFound } from "next/navigation";
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) {
notFound(); // прерывает рендер и показывает ближайший not-found.tsx
}
return <article>{post.title}</article>;
}Для страниц, у которых список адресов известен заранее, добавьте export const dynamicParams = false;: тогда любой параметр вне generateStaticParams вернёт 404. Следите за стримингом: если ответ уже начал отправляться (например, из-за loading.tsx), заголовки ушли, и статус 404 не гарантирован — в документации Next.js для таких случаев описана вставка noindex. Проверяйте реальный ответ в проверке HTTP-статуса.
Pages Router
В проектах на pages/ создайте pages/404.js (или 404.tsx). Он генерируется статически при сборке, поэтому getServerSideProps в нём недоступен. Для динамических страниц верните { notFound: true } из getStaticProps или getServerSideProps. Страница 500 настраивается через pages/500.js.
export async function getServerSideProps({ params }) {
const post = await getPost(params.slug);
if (!post) {
return { notFound: true };
}
return { props: { post } };
}Редиректы в next.config.js
module.exports = {
async redirects() {
return [
// 308 (постоянный)
{ source: "/old-page", destination: "/new-page", permanent: true },
// сохраняем хвост пути
{ source: "/blog/:slug", destination: "/articles/:slug", permanent: true },
{ source: "/docs/:path*", destination: "/help/:path*", permanent: true },
// 307 (временный)
{ source: "/promo", destination: "/sale", permanent: false },
// конкретный код вместо permanent
{ source: "/legacy", destination: "/modern", statusCode: 301 },
];
},
};permanent: trueотвечает кодом 308,permanent: false— 307. Оба сохраняют HTTP-метод запроса. Для поисковых систем 308 — такой же постоянный редирект, как 301, см. /http/308.- Если нужен именно 301 или 302, используйте
statusCode. Одновременно сpermanentего указывать нельзя. - Редиректы из
next.config.jsпроверяются до файловой системы и до middleware. - В режиме
output: "export"(статический экспорт)redirects()не работает — редиректы придётся настраивать на хостинге, например в nginx.
Middleware
Когда правила зависят от cookie, заголовков или хранятся во внешнем списке, используйте middleware. В новых версиях Next.js (16 и выше) файл middleware.ts переименован в proxy.ts — уточните в документации вашей версии.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const redirects: Record<string, string> = {
"/old-page": "/new-page",
"/old-catalog": "/catalog",
};
export function middleware(request: NextRequest) {
const target = redirects[request.nextUrl.pathname];
if (target) {
return NextResponse.redirect(new URL(target, request.url), 301);
}
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};В серверных компонентах для редиректов есть redirect() (временный, по умолчанию 307) и permanentRedirect() (308) из next/navigation. Большие таблицы редиректов удобнее генерировать: генератор редиректов.
Частые ошибки
- Страница-«ловушка»
[...slug]рисует текст «не найдено», но не вызываетnotFound()— статус остаётся 200. redirect()вместоpermanentRedirect()при переезде: временный редирект вместо постоянного.- Перенаправление всех 404 на главную в middleware — это soft 404.
- Matcher middleware захватывает
_next/staticи картинки — лишняя нагрузка и поломка статики. - Конфликт
trailingSlashи редиректов со слешем: цепочки и циклы.
Как найти 404 в Next.js
- Логи платформы (Vercel, Node-сервер, nginx перед приложением) и отчёты Вебмастера и Search Console.
- Проверка битых ссылок для отдельных страниц сайта.
- Трекер на странице not-found — фиксирует фактические переходы посетителей.
Подключение Monitor404 к Next.js
Добавьте скрипт в app/not-found.tsx через next/script:
import Script from "next/script";
export default function NotFound() {
return (
<main>
<h1>Страница не найдена</h1>
<Script
src="https://collect.monitor404.ru/collect/ВАШ_ТОКЕН/script.js"
strategy="afterInteractive"
/>
</main>
);
}Для Pages Router положите тот же Script в pages/404.js. Чтобы ловить ошибки на всех страницах, подключите скрипт с ?mode=auto в корневом layout.tsx. Если вам важно знать реальный HTTP-статус, отправляйте события с сервера через API Monitor404 с HMAC-подписью.
Частые вопросы
Как сделать страницу 404 в Next.js?
В App Router создайте app/not-found.tsx, в Pages Router — pages/404.js. Для конкретных страниц вызывайте notFound() из next/navigation (App Router) или возвращайте { notFound: true } из getStaticProps / getServerSideProps.
Какой код у редиректа с permanent: true в Next.js?
308, а для permanent: false — 307. Если нужен 301 или 302, укажите statusCode вместо permanent.
Как сделать редирект в Next.js?
Для статических правил — redirects() в next.config.js; для динамических — middleware с NextResponse.redirect(); в серверных компонентах — redirect() или permanentRedirect().
Почему страница not-found в Next.js отдаёт 200?
Возможные причины: кастомная catch-all страница не вызывает notFound(), ответ уже начал стримиться, либо статус подменяет прокси или CDN. Проверьте ответ в проверке HTTP-статуса.