Middleware

middleware.ts: перенаправления, перезапись адресов, заголовки и cookie перед каждым запросом, с config.matcher.

Middleware выполняет код до завершения запроса. Он может перенаправлять запросы, переписывать пути, задавать заголовки и cookie или самостоятельно возвращать ответ.

Соглашение

Создайте middleware.ts в корне проекта (или в src/), рядом с app/. Экспортируйте функцию middleware (или используйте экспорт по умолчанию) и необязательный config.

middleware.tsts
import { NextResponse, type NextRequest } from "@textjs/app/server";export function middleware(request: NextRequest) {  if (!request.cookies.has("session")) {    return NextResponse.redirect(new URL("/login", request.url));  }  return NextResponse.next();}export const config = {  matcher: ["/dashboard/:path*", "/api/:path*"],};

Middleware выполняется перед обработчиками маршрутов, выдачей статических файлов и страниц консоли при запуске text dev, text start и с любым серверным адаптером развёртывания. Он использует веб-API Request и Response, поэтому один и тот же файл работает на Node, Bun, Vercel Edge и Cloudflare Workers.

Сопоставление путей

Без config.matcher middleware выполняется для каждого запроса. Шаблоны сопоставления используют тот же синтаксис, что и Next.js:

ШаблонСовпадения
/about/about и /about/
/blog/:slug/blog/hello
/docs/:path*/docs, /docs/a/b
/a/:rest+/a/b, но не /a
/user/:id(\\d+)/user/42, но не /user/ada
/((?!api/).*)Всё, кроме /api/*

Добавьте условия с помощью has и missing (заголовок, cookie, параметр запроса или хост):

middleware.tsts
export const config = {  matcher: [{ source: "/beta/:path*", has: [{ type: "cookie", key: "beta", value: "1" }] }],};

Шаблоны применяются к пути без basePath. В text dev собственные запросы модулей Vite (/@vite/*, исходные файлы) никогда не запускают middleware.

Каждый хост определяет единый канонический путь до запуска middleware и использует его для сопоставления, request.nextUrl и поиска маршрута, обработчика и файла. Процентные последовательности декодируются, а путь кодируется заново так, как его записывает new URL(); сегменты . и .. разрешаются, а повторяющиеся и завершающие косые черты удаляются. /ad%6din//users/ поступает в middleware как /admin/users, а /café остаётся /caf%C3%A9. Шаблон с завершающей косой чертой, например /admin/, соответствует /admin. Запросы, в пути которых есть закодированная прямая или обратная косая черта (%2F, %5C), управляющий символ или некорректное процентное кодирование, получают 400. Маршруты чувствительны к регистру: /Admin не соответствует /admin, а статические файлы выдаются только по их точному имени.

Сопоставляйте URL, по которым выдаётся файл, а не только его имя. Cloudflare Pages выдаёт report.html и по адресу /report, поэтому шаблон для /report.html не защищает /report. Укажите оба адреса, matcher: ["/report", "/report.html"], или храните защищённые файлы в каталоге и сопоставляйте его, например /reports/:path* для файлов в public/reports/.

NextResponse

APIДействие
NextResponse.next()Переход к маршруту
NextResponse.redirect(url, status?)307 по умолчанию; url должен быть абсолютным
NextResponse.rewrite(url)Выдача другого пути; внешние источники проксируются
NextResponse.json(body, init?)Ответ в формате JSON
new Response(...)Ответ с любым содержимым

Не возвращайте ничего, чтобы продолжить без изменений. next() и rewrite() принимают { status } для изменения статуса итогового ответа. Только ответы, созданные через NextResponse.next() / NextResponse.rewrite(), управляют маршрутизацией: любой другой Response, включая возвращённый из fetch(), отправляется клиенту без изменений, но с удалёнными заголовками x-middleware-*.

request.nextUrl исключает basePath из pathname, а его href и toString() включают его. Чтобы остаться внутри приложения, клонируйте его: const url = request.nextUrl.clone(); url.pathname = "/login"; return NextResponse.redirect(url);.

middleware.tsts
export function middleware(request: NextRequest) {  if (request.nextUrl.pathname === "/status") {    return NextResponse.rewrite(new URL("/api/health", request.url));  }}

Заголовки

Передайте request.headers, чтобы изменить заголовки, которые получат последующие обработчики, и headers, чтобы добавить заголовки ответа:

middleware.tsts
export function middleware(request: NextRequest) {  const headers = new Headers(request.headers);  headers.set("x-user", "ada");  const response = NextResponse.next({ request: { headers } });  response.headers.set("x-frame", "▼");  return response;}

Заголовки ответа из middleware добавляются к итоговому ответу, если обработчик маршрута не задаёт тот же заголовок.

Cookie

request.cookies читает заголовок Cookie; response.cookies записывает Set-Cookie.

middleware.tsts
export function middleware(request: NextRequest) {  const theme = request.cookies.get("theme")?.value ?? "geistDark";  const response = NextResponse.next();  response.cookies.set("theme", theme, { httpOnly: true, sameSite: "lax", maxAge: 60 * 60 * 24 });  return response;}

Cookie, заданные в ответе next() или rewrite(), доступны и обработчику маршрута в том же запросе.

Фоновая работа

У второго аргумента есть waitUntil(), который сохраняет работу бессерверной среды выполнения после отправки ответа:

middleware.tsts
import type { NextFetchEvent, NextRequest } from "@textjs/app/server";export function middleware(request: NextRequest, event: NextFetchEvent) {  event.waitUntil(fetch("https://analytics.example.com", { method: "POST", body: request.url }));}

Примечания

  • Страницы с клиентским рендерингом рендерятся в браузере, поэтому переписывание пути на другую страницу меняет выдаваемый файл, а не маршрут, который рендерит консольное приложение. Переписывание путей на обработчики маршрутов, статические файлы и внешние источники работает ожидаемым образом.
  • Middleware может читать тело запроса; обработчики маршрутов по-прежнему получают его. Тело запросов, исключённых шаблоном сопоставления, никогда не затрагивается.
  • request.url всегда содержит источник сервера: адреса запросов вроде //other.host/x нормализуются до одной начальной косой черты.
  • Статический адаптер не может выполнять middleware.
Изменить эту страницу на GitHub