Middleware
middleware.ts: chuyển hướng, viết lại URL, header và cookie trước mỗi yêu cầu, với config.matcher.
Middleware chạy mã trước khi một yêu cầu hoàn tất. Nó có thể chuyển hướng, viết lại, đặt header và cookie, hoặc tự trả về phản hồi.
Quy ước
Tạo middleware.ts trong thư mục gốc của dự án (hoặc src/), cạnh app/. Xuất một hàm middleware (hoặc một export mặc định) và config tùy chọn.
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 chạy trước route handler, tệp tĩnh và trang console, trên text dev, text start và mọi adapter triển khai máy chủ. Nó dùng Request và Response của Web, nên cùng một tệp chạy trên Node, Bun, Vercel Edge và Cloudflare Workers.
Khớp đường dẫn
Khi không có config.matcher, middleware chạy cho mọi yêu cầu. Matcher dùng cùng cú pháp với Next.js:
| Matcher | Khớp với |
|---|---|
/about | /about và /about/ |
/blog/:slug | /blog/hello |
/docs/:path* | /docs, /docs/a/b |
/a/:rest+ | /a/b, không khớp /a |
/user/:id(\\d+) | /user/42, không khớp /user/ada |
/((?!api/).*) | Mọi đường dẫn trừ /api/* |
Thêm điều kiện bằng has và missing (header, cookie, truy vấn hoặc host):
export const config = { matcher: [{ source: "/beta/:path*", has: [{ type: "cookie", key: "beta", value: "1" }] }],};Matcher áp dụng cho pathname không có basePath. Trong text dev, yêu cầu module riêng của Vite (/@vite/*, tệp nguồn) không bao giờ chạy middleware.
Mỗi host xác định một pathname chuẩn trước khi middleware chạy và dùng nó để khớp, cho request.nextUrl và để tra cứu route, handler và tệp. Các chuỗi mã hóa phần trăm được giải mã rồi đường dẫn được mã hóa lại theo cách new URL() ghi nó, các đoạn . và .. được phân giải, còn dấu gạch chéo lặp lại và ở cuối bị loại bỏ. /ad%6din//users/ đến middleware dưới dạng /admin/users, còn /café giữ dạng /caf%C3%A9. Matcher có dấu gạch chéo ở cuối, như /admin/, khớp với /admin. Yêu cầu có đường dẫn chứa dấu gạch chéo hoặc gạch chéo ngược đã mã hóa (%2F, %5C), ký tự điều khiển hoặc mã hóa phần trăm không hợp lệ nhận mã 400. Route phân biệt chữ hoa và chữ thường: /Admin không khớp với /admin, và tệp tĩnh chỉ được phục vụ dưới đúng tên của chúng.
Khớp các URL dùng để phục vụ tệp, không chỉ tên tệp. Cloudflare Pages cũng phục vụ report.html tại /report, nên matcher cho /report.html không bảo vệ /report. Liệt kê cả hai, matcher: ["/report", "/report.html"], hoặc lưu tệp cần bảo vệ trong một thư mục và khớp thư mục đó, chẳng hạn /reports/:path* cho tệp trong public/reports/.
NextResponse
| API | Tác dụng |
|---|---|
NextResponse.next() | Tiếp tục đến route |
NextResponse.redirect(url, status?) | Mặc định là 307; url phải là URL tuyệt đối |
NextResponse.rewrite(url) | Phục vụ đường dẫn khác; chuyển tiếp qua proxy với origin bên ngoài |
NextResponse.json(body, init?) | Phản hồi bằng JSON |
new Response(...) | Phản hồi bằng nội dung bất kỳ |
Không trả về gì để tiếp tục mà không thay đổi. next() và rewrite() chấp nhận { status } để đổi trạng thái của phản hồi cuối cùng. Chỉ phản hồi do NextResponse.next() / NextResponse.rewrite() tạo ra mới điều khiển định tuyến: mọi Response khác, kể cả phản hồi trả về từ fetch(), được gửi nguyên trạng đến máy khách sau khi loại bỏ các header x-middleware-*.
request.nextUrl loại basePath khỏi pathname, còn href và toString() của nó bao gồm giá trị này. Để giữ URL trong ứng dụng, sao chép nó: 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)); }}Header
Truyền request.headers để thay đổi nội dung mà các handler phía sau nhận, và headers để thêm header phản hồi:
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;}Header phản hồi từ middleware được thêm vào phản hồi cuối cùng, trừ khi route handler đặt cùng header đó.
Cookie
request.cookies đọc header Cookie; response.cookies ghi 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;}Route handler cũng có thể thấy cookie đặt trên phản hồi next() hoặc rewrite() trong cùng yêu cầu.
Tác vụ nền
Đối số thứ hai có waitUntil(), giúp duy trì hoạt động của môi trường chạy serverless sau khi gửi phản hồi:
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 }));}Ghi chú
- Trang render phía máy khách render trong trình duyệt, nên viết lại sang trang khác thay đổi tệp được phục vụ, không thay đổi route mà ứng dụng console render. Viết lại sang route handler, tệp tĩnh và origin bên ngoài hoạt động như mong đợi.
- Middleware có thể đọc phần thân yêu cầu; route handler vẫn nhận được phần thân đó. Phần thân của yêu cầu mà matcher loại trừ không bao giờ bị tác động.
request.urlluôn có origin của máy chủ: đích yêu cầu như//other.host/xđược chuẩn hóa để chỉ có một dấu gạch chéo ở đầu.- Adapter tĩnh không thể chạy middleware.