Middleware
middleware.ts: pengalihan, rewrite, header, dan cookie sebelum setiap permintaan, dengan config.matcher.
Middleware menjalankan kode sebelum permintaan selesai. Middleware dapat mengalihkan, melakukan rewrite, mengatur header dan cookie, atau mengembalikan respons sendiri.
Konvensi
Buat middleware.ts di direktori root proyek (atau src/), di sebelah app/. Ekspor fungsi middleware (atau ekspor default) dan config opsional.
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 berjalan sebelum route handler, file statis dan halaman konsol, pada text dev, text start dan setiap adapter deploy server. Middleware menggunakan Request dan Response Web, sehingga file yang sama berjalan di Node, Bun, Vercel Edge dan Cloudflare Workers.
Pencocokan path
Tanpa config.matcher, middleware berjalan untuk setiap permintaan. Matcher menggunakan sintaks yang sama dengan Next.js:
| Matcher | Kecocokan |
|---|---|
/about | /about dan /about/ |
/blog/:slug | /blog/hello |
/docs/:path* | /docs, /docs/a/b |
/a/:rest+ | /a/b, bukan /a |
/user/:id(\\d+) | /user/42, bukan /user/ada |
/((?!api/).*) | Semua kecuali /api/* |
Tambahkan kondisi dengan has dan missing (header, cookie, kueri atau host):
export const config = { matcher: [{ source: "/beta/:path*", has: [{ type: "cookie", key: "beta", value: "1" }] }],};Matcher berlaku pada pathname tanpa basePath. Pada text dev, permintaan modul Vite sendiri (/@vite/*, file sumber) tidak pernah menjalankan middleware.
Setiap host menentukan satu pathname kanonis sebelum middleware berjalan, lalu menggunakannya untuk pencocokan, request.nextUrl serta pencarian route, handler dan file. Escape persen didekode dan path dikodekan ulang sesuai cara new URL() menuliskannya, segmen . dan .. diselesaikan, serta garis miring berulang dan di akhir dihapus. /ad%6din//users/ mencapai middleware sebagai /admin/users, sementara /café tetap /caf%C3%A9. Matcher yang ditulis dengan garis miring di akhir, seperti /admin/, cocok dengan /admin. Permintaan dengan path yang memuat garis miring atau garis miring terbalik terkode (%2F, %5C), karakter kontrol atau pengodean persen yang tidak valid mendapatkan 400. Route membedakan huruf besar dan kecil: /Admin tidak cocok dengan /admin, dan file statis hanya disajikan dengan nama persisnya.
Cocokkan URL tempat file disajikan, bukan hanya nama filenya. Cloudflare Pages juga menyajikan report.html di /report, sehingga matcher untuk /report.html tidak melindungi /report. Cantumkan keduanya, matcher: ["/report", "/report.html"], atau simpan file yang dilindungi dalam direktori dan cocokkan direktori tersebut, seperti /reports/:path* untuk file di public/reports/.
NextResponse
| API | Efek |
|---|---|
NextResponse.next() | Lanjutkan ke route |
NextResponse.redirect(url, status?) | 307 secara default; url harus absolut |
NextResponse.rewrite(url) | Sajikan path lain; origin eksternal diteruskan melalui proksi |
NextResponse.json(body, init?) | Berikan respons JSON |
new Response(...) | Berikan respons apa pun |
Jangan kembalikan apa pun untuk melanjutkan tanpa perubahan. next() dan rewrite() menerima { status } untuk mengubah status respons akhir. Hanya respons yang dibuat oleh NextResponse.next() / NextResponse.rewrite() yang mengarahkan routing: Response lainnya, termasuk yang dikembalikan oleh fetch(), dikirim ke klien apa adanya setelah header x-middleware-* dihapus.
request.nextUrl mengecualikan basePath dari pathname, sementara href dan toString() menyertakannya. Agar tetap berada di dalam aplikasi, buat salinannya: 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
Teruskan request.headers untuk mengubah apa yang diterima handler berikutnya, dan headers untuk menambahkan header respons:
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 respons dari middleware ditambahkan ke respons akhir kecuali route handler mengatur header yang sama.
Cookie
request.cookies membaca header Cookie; response.cookies menulis 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;}Cookie yang diatur pada respons next() atau rewrite() juga terlihat oleh route handler dalam permintaan yang sama.
Pekerjaan latar belakang
Argumen kedua memiliki waitUntil(), yang menjaga runtime serverless tetap aktif setelah respons dikirim:
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 }));}Catatan
- Halaman yang di-render di sisi klien di-render di browser, sehingga rewrite ke halaman lain mengubah file yang disajikan, bukan route yang di-render aplikasi konsol. Rewrite ke route handler, file statis dan origin eksternal berperilaku sesuai harapan.
- Middleware dapat membaca body permintaan; route handler tetap menerimanya. Body permintaan yang dikecualikan oleh matcher tidak pernah disentuh.
request.urlselalu memiliki origin server: target permintaan seperti//other.host/xdinormalisasi menjadi satu garis miring di awal.- Adapter statis tidak dapat menjalankan middleware.