У промисловій розробці критично важливо контролювати вхідний трафік до того, як ресурси сервера додатків будуть витрачені на рендеринг компонентів або виконання ресурсомістких обчислень:
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().