В промышленной разработке критически важно контролировать входящий трафик до того, как ресурсы сервера приложений будут потрачены на рендеринг компонентов или выполнение ресурсоемких вычислений:
x-vercel-ip-country) с перенаправлением на соответствующий префикс.Next.js решает эти задачи через специальный шлюзовой механизм — Next.js Middleware. Это код, исполняемый на пограничных CDN-узлах (Edge Network) для каждого входящего HTTP-запроса до разрешения дерева маршрутов и проверки серверного кэша.
Файл middleware.ts объявляется строго в корне проекта (на одном уровне с директориями app или src).
Ключевая архитектурная особенность Middleware в Next.js — исполнение в Edge Runtime. Это изолированная песочница на базе движка V8 с доступом к стандартным Web API (fetch, Request, Response, Web Crypto, Headers), но без нативных системных библиотек Node.js (fs, child_process, TCP-сокетов).
За счет работы на периферии сети Middleware обеспечивает запуск быстрее 10 мс, выполняя фильтрацию запросов еще до того, как пакет данных достигнет основного дата-центра.
Входящий HTTP-запрос перехватывается на ближайшей по географии к пользователю пограничной CDN ноде.
Движок сверяет путь с регулярным выражением config.matcher. Статические файлы, картинки и шрифты отсекаются.
Быстрая валидация JWT-токена или сессионной куки через Web Crypto API без тяжелых запросов к базе данных.
Генерация случайного криптографического nonce, добавление заголовков x-user-id в объект NextResponse.next().
Принятие решения: пропуск дальше (NextResponse.next), перенаправление (redirect) или внутренняя подмена пути (rewrite).
В чем заключается фундаментальное различие между NextResponse.redirect() и NextResponse.rewrite()?
Файл middleware.ts экспортирует функцию middleware и объект конфигурации config.matcher:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('session_token')?.value;
// Авторизационный гард для закрытых разделов
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
// Конфигурация путей, на которых должен срабатывать Middleware
export const config = {
matcher: [
/*
* Исключаем служебные пути для оптимизации производительности:
* - api (если не требуется глобальный middleware)
* - _next/static (статические JS/CSS файлы)
* - _next/image (оптимизированные изображения)
* - favicon.ico, sitemap.xml, robots.txt
*/
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
};Next.js позволяет обогащать входящий запрос дополнительными заголовками (например, инжектировать идентификатор пользователя или криптографический nonce для CSP):
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Клонируем заголовки входящего запроса
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-gateway-timestamp', Date.now().toString());
requestHeaders.set('x-client-ip', request.ip ?? 'unknown');
// Передаем измененные заголовки в серверные компоненты
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}Почему в Next.js Middleware категорически не рекомендуется выполнять тяжелые запросы к реляционным базам данных через Prisma или pg?
Паттерн построения SaaS-платформ, где клиент открывает адрес company.platform.com, а сервер незаметно перенаправляет запрос на сегмент app/tenants/[tenant]/page.tsx:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const hostname = request.headers.get('host') ?? '';
const pathname = request.nextUrl.pathname;
// Извлекаем субдомен (например, acme.platform.com -> acme)
const currentHost = hostname.replace(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`, '');
// Если это субдомен, проксируем на внутренний сегмент
if (currentHost && currentHost !== 'www' && currentHost !== 'localhost:3000') {
return NextResponse.rewrite(
new URL(`/tenants/${currentHost}${pathname}`, request.url)
);
}
return NextResponse.next();
}Если в функции Middleware настроено перенаправление неавторизованных пользователей на /login, но сам адрес /login не исключен из правила config.matcher, Middleware будет повторно вызывать сам себя до ошибки ERR_TOO_MANY_REDIRECTS.
Как избежать: Всегда проверяйте текущий путь перед вызовом редиректа: if (!token && !request.nextUrl.pathname.startsWith('/login')).
Частая ошибка — мутировать request.headers.set(...), а затем вызвать пустой NextResponse.next(). В этом случае изменения не дойдут до Server Components.
Как избежать: Передавайте объект request: { headers: requestHeaders } непосредственно в аргументы метода NextResponse.next().