Пошук уроків, статей та іншого контенту
Перехоплюйте запити через Middleware або Proxy для редиректів, переписувань, авторизації та перевірок.
Middleware — це код, який виконується між отриманням HTTP-запиту та обробкою цього запиту маршрутом Next.js. Він може перевірити запит і:
перенаправити користувача на іншу адресу;
переписати URL без зміни адреси в браузері;
перевірити cookie, заголовки або шлях;
додати чи змінити заголовки відповіді;
пропустити запит далі.
У нових версіях Next.js Middleware перейменовано на Proxy. Назва змінилася, але призначення залишилося подібним:
файл proxy.ts;
функція proxy;
імпорти NextRequest і NextResponse;
конфігурація matcher.
У попередніх версіях використовуються:
файл middleware.ts;
функція middleware.
Цей урок показує сучасний варіант із proxy.ts, а наприкінці наведено відповідність для старішого синтаксису.
Файл proxy.ts потрібно розмістити в корені проєкту, поруч із app або pages. Якщо використовується каталог
srcsrc/proxy.tsБазова структура:
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
return NextResponse.next();
}NextResponse.next() означає: продовжити обробку запиту стандартним маршрутом Next.js.
Proxy виконується до того, як запит потрапить у відповідний route handler або сторінку.
За замовчуванням Proxy може виконуватися для великої кількості запитів. Щоб обмежити його лише потрібними маршрутами, використовуйте config.matcher.
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
};Такий Proxy спрацює для:
/dashboard;
/dashboard/settings;
/admin;
/admin/users/42.
Він не спрацює для /, /login або /about.
matcher використовує шаблони маршрутів Next.js. Суфікс :path* означає поточний шлях і всі вкладені шляхи.
Якщо Proxy застосовується до всіх сторінок, зазвичай виключаються внутрішні ресурси Next.js:
export const config = {
matcher: [
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};Цей шаблон виключає:
маршрути /api;
статичні файли Next.js;
оптимізовані зображення;
favicon.ico.
Краще використовувати конкретний matcher, якщо перевірка потрібна лише для кількох маршрутів.
Редирект змінює адресу в браузері. Браузер отримує HTTP-відповідь і виконує новий запит за іншою адресою.
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname === "/old-dashboard") {
return NextResponse.redirect(
new URL("/dashboard", request.url),
);
}
return NextResponse.next();
}
export const config = {
matcher: ["/old-dashboard", "/dashboard/:path*"],
};Після переходу на /old-dashboard користувач побачить у браузері адресу /dashboard.
Для тимчасового редиректу можна передати статус:
return NextResponse.redirect(
new URL("/temporary-page", request.url),
307,
);Зазвичай достатньо стандартного статусу, який використовує NextResponse.redirect.
Переписування змінює маршрут, який обробляє запит, але адреса в браузері залишається тією самою.
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
if (request.nextUrl.pathname === "/help") {
return NextResponse.rewrite(
new URL("/support", request.url),
);
}
return NextResponse.next();
}
export const config = {
matcher: ["/help"],
};Користувач відкриває /help, але фактично сторінку обробляє маршрут /support.
| Операція | Адреса в браузері | Результат | |---|---|---| | redirect | змінюється | браузер виконує новий запит | | rewrite | не змінюється | поточний запит обробляє інший маршрут |
Для перенесення сторінки на нову публічну адресу використовуйте redirect. Для внутрішнього зіставлення URL — rewrite.
Proxy часто використовується для попередньої перевірки доступу до приватних маршрутів.
Cookie можна прочитати через request.cookies:
const session = request.cookies.get("session");Метод has перевіряє, чи існує cookie:
const hasSession = request.cookies.has("session");Повний приклад:
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
const { pathname, search } = request.nextUrl;
const isPrivateRoute =
pathname.startsWith("/dashboard") ||
pathname.startsWith("/admin");
if (!isPrivateRoute) {
return NextResponse.next();
}
const hasSession = request.cookies.has("session");
if (hasSession) {
return NextResponse.next();
}
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("from", `${pathname}${search}`);
return NextResponse.redirect(loginUrl);
}
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
};Алгоритм роботи:
Proxy отримує URL запиту.
Визначає, чи є маршрут приватним.
Перевіряє наявність cookie session.
Якщо сесія є — передає запит далі.
Якщо сесії немає — перенаправляє на /login.
Початковий шлях зберігається в параметрі from.
Після входу застосунок може повернути користувача на значення from, але це значення потрібно перевіряти, щоб не створити небезпечний зовнішній редирект.
Наявність cookie ще не доводить, що користувач авторизований. У реальному застосунку потрібно:
перевіряти підпис або токен сесії;
перевіряти термін дії сесії;
враховувати права користувача;
повторно перевіряти доступ у серверному коді або на рівні отримання даних.
Proxy зручний для швидкого відсікання неавторизованих запитів, але не повинен бути єдиним рівнем захисту важливих даних.
Proxy може перевіряти заголовки запиту. Наприклад, можна заборонити доступ до внутрішнього маршруту без спеціального заголовка:
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
const internalKey = request.headers.get("x-internal-key");
if (internalKey !== process.env.INTERNAL_KEY) {
return new NextResponse("Forbidden", { status: 403 });
}
return NextResponse.next();
}
export const config = {
matcher: ["/internal/:path*"],
};Таке порівняння підходить для простого захисту внутрішнього маршруту, але секрет не можна вбудовувати в клієнтський код. Значення process.env.INTERNAL_KEY має бути доступним лише на сервері.
До відповіді, яку повертає наступний маршрут, можна додати заголовки:
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
const response = NextResponse.next();
response.headers.set("x-app-version", "1");
return response;
}Цей заголовок буде додано до відповіді. У Proxy не слід без потреби змінювати системні заголовки Next.js або заголовки, пов’язані з кешуванням.
Нижче Proxy виконує кілька типових завдань:
перенаправляє стару адресу;
перенаправляє неавторизованих користувачів;
переписує /preview на внутрішню сторінку;
пропускає всі інші запити без змін.
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
const { pathname, search } = request.nextUrl;
if (pathname === "/old-dashboard") {
return NextResponse.redirect(
new URL("/dashboard", request.url),
);
}
if (pathname === "/preview") {
return NextResponse.rewrite(
new URL("/preview-page", request.url),
);
}
const isPrivateRoute =
pathname.startsWith("/dashboard") ||
pathname.startsWith("/admin");
if (isPrivateRoute && !request.cookies.has("session")) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("from", `${pathname}${search}`);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
export const config = {
matcher: [
"/old-dashboard",
"/preview",
"/dashboard/:path*",
"/admin/:path*",
],
};Цей файл можна використовувати в проєкті з App Router або Pages Router. Сам Proxy не залежить від того, де саме реалізовані сторінки.
Якщо проєкт використовує старішу версію Next.js, аналогічний код має називатися middleware.ts, а функція — middleware:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === "/old-dashboard") {
return NextResponse.redirect(
new URL("/dashboard", request.url),
);
}
return NextResponse.next();
}
export const config = {
matcher: ["/old-dashboard"],
};Для конкретного проєкту орієнтуйтеся на версію Next.js і документацію, яку використовує цей проєкт. Не потрібно одночасно створювати middleware.ts і proxy.ts для однакової логіки.
Під час обробки запиту Next.js спочатку визначає, чи відповідає URL конфігурації matcher. Якщо відповідає, виконується Proxy.
Proxy може завершити обробку одним із способів:
NextResponse.next() — передати запит далі;
NextResponse.redirect() — виконати редирект;
NextResponse.rewrite() — передати запит іншому маршруту;
повернути об’єкт Response — завершити запит власною відповіддю.
Якщо Proxy повертає редирект або власну відповідь, наступний маршрут не обробляє цей запит.
Надто широкий matcher може змусити Proxy обробляти статичні ресурси, зображення та інші службові запити.
Краще обмежувати маршрути:
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
};Якщо Proxy перенаправляє на /login, але сам також перевіряє /login, може виникнути цикл редиректів.
Маршрут сторінки входу не повинен входити до приватних маршрутів або має бути явно виключений із перевірки.
rewrite не змінює адресу в браузері. Якщо користувач має перейти на нову публічну адресу, використовуйте redirect.
Cookie може бути підроблена або протермінована. Перевіряйте її значення та повторно контролюйте права доступу під час роботи з даними.
Секретні значення не можна передавати через змінні з префіксом NEXT_PUBLIC_ і не можна вбудовувати у компоненти, які виконуються в браузері.
Proxy виконується часто, тому не варто розміщувати в ньому складні обчислення або зайві запити до зовнішніх сервісів. Використовуйте його для ранньої перевірки маршруту, cookie та заголовків.
У сучасному Next.js Middleware перейменовано на Proxy.
Сучасний файл має назву proxy.ts, а функція — proxy.
NextResponse.next() передає запит далі.
NextResponse.redirect() змінює адресу в браузері.
NextResponse.rewrite() змінює внутрішній маршрут без зміни адреси.
config.matcher обмежує маршрути, для яких виконується Proxy.
Proxy зручно використовувати для редиректів, переписувань і попередньої перевірки авторизації.
Перевірка в Proxy не повинна бути єдиним захистом важливих даних.