middleware
middleware.ts: تغییرمسیرها، بازنویسیها، هدرها و کوکیها پیش از هر درخواست، با config.matcher.
middleware پیش از تکمیل درخواست، کد را اجرا میکند. میتواند تغییر مسیر دهد، مسیر را بازنویسی کند، هدرها و کوکیها را تنظیم کند یا خودش پاسخی برگرداند.
قرارداد
فایل middleware.ts را در ریشه پروژه (یا src/)، کنار app/ ایجاد کنید. یک تابع middleware (یا یک خروجی پیشفرض) و یک config اختیاری صادر کنید.
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 و هر آداپتور استقرار سرور اجرا میشود. از 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 شرط اضافه کنید (هدر، کوکی، پرسوجو یا میزبان):
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/ بهشکل /admin/users به middleware میرسد، درحالیکه /café بهشکل /caf%C3%A9 باقی میماند. تطبیقدهندهای که با اسلش انتهایی نوشته شده باشد، مانند /admin/، با /admin منطبق میشود. درخواستهایی که مسیرشان اسلش یا بکاسلش کدگذاریشده (%2F، %5C)، نویسه کنترلی یا کدگذاری درصدی نامعتبر داشته باشد، پاسخ 400 میگیرند. مسیرها به بزرگی و کوچکی حروف حساساند: /Admin با /admin منطبق نمیشود و فایلهای ایستا فقط با نام دقیق خود ارائه میشوند.
نشانیهایی را که فایل در آنها ارائه میشود تطبیق دهید، نه فقط نام فایل را. 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);.
export function middleware(request: NextRequest) { if (request.nextUrl.pathname === "/status") { return NextResponse.rewrite(new URL("/api/health", request.url)); }}هدرها
برای تغییر آنچه کنترلگرهای پاییندست دریافت میکنند، request.headers و برای افزودن هدرهای پاسخ، headers را ارسال کنید:
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 به پاسخ نهایی اضافه میشوند، مگر اینکه کنترلگر مسیر همان هدر را تنظیم کند.
کوکیها
ویژگی request.cookies هدر Cookie را میخواند؛ ویژگی response.cookies هدر Set-Cookie را مینویسد.
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;}کوکیهای تنظیمشده در پاسخ next() یا rewrite() برای کنترلگر مسیر در همان درخواست نیز قابل مشاهدهاند.
کار پسزمینه
آرگومان دوم، waitUntil() را دارد که محیطهای اجرای بدون سرور را پس از ارسال پاسخ فعال نگه میدارد:
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 را اجرا کند.