Middleware Matcher
Membatasi middleware hanya untuk path tertentu agar hemat.
Masalah: middleware tanpa rem itu boros
Secara default, middleware.ts berjalan untuk setiap request yang masuk ke aplikasimu. Termasuk request untuk gambar, file CSS, JavaScript hasil build (/_next/static), bahkan /favicon.ico. Kalau middleware-mu cuma mengecek cookie untuk halaman dashboard, semua eksekusi itu sia-sia.
Di production yang di-host di platform Edge (seperti Vercel), setiap eksekusi middleware dihitung. Ribuan request gambar per hari berarti ribuan eksekusi middleware yang tidak menghasilkan apa-apa. Matcher adalah remnya: ia membatasi middleware hanya berjalan untuk path yang kamu tentukan.
Contoh 1: matcher positif, daftarkan yang dijaga
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const token = req.cookies.get("token");
if (!token) {
return NextResponse.redirect(new URL("/login", req.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
};Dengan matcher ini, fungsi middleware tidak akan pernah dipanggil untuk /, /blog, /favicon.ico, atau file statis. Ia hanya hidup di bawah /dashboard dan /admin. Perhatikan bahwa pengecekan startsWith di dalam fungsi jadi tidak wajib lagi, karena matcher sudah menyaring path-nya, tapi membiarkannya sebagai pengaman ganda tidak ada salahnya.
Contoh 2: pola negatif, kecuali yang ini
Kadang yang ingin dikecualikan lebih sedikit daripada yang ingin dicakup. Misalnya middleware analitik yang harus jalan di semua halaman tapi tidak untuk API dan aset statis:
// middleware.ts
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};Pola (?!...) adalah regex negative lookahead: "cocokkan semua path KECUALI yang diawali api, _next/static, _next/image, atau favicon.ico". Ini pola standar yang direkomendasikan dokumentasi Next.js untuk middleware yang memang harus global.
Sintaks path yang perlu dihafal
/dashboard/:path*: semua yang di bawah/dashboard, termasuk/dashboardsendiri dan/dashboard/pengaturan/profil./blog/:slug: tepat satu segmen dinamis, cocok untuk/blog/abctapi tidak untuk/blog/abc/komentar./blog/:slug*: satu segmen atau lebih, jadi/blog/abc/komentarikut tercakup.- Pola negatif dengan regex untuk pengecualian kompleks.
Alternatif: penyaringan manual di dalam fungsi
Matcher bukan satu-satunya cara. Kamu juga bisa memeriksa path di dalam fungsi middleware dan langsung keluar untuk path yang tidak perlu diperiksa:
// middleware.ts (tanpa config.matcher)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const JALUR_PUBLIK = ["/", "/login", "/tentang"];
export function middleware(req: NextRequest) {
if (JALUR_PUBLIK.includes(req.nextUrl.pathname)) {
return NextResponse.next();
}
const token = req.cookies.get("token");
if (!token) {
return NextResponse.redirect(new URL("/login", req.url));
}
return NextResponse.next();
}Kekurangannya: fungsi middleware tetap DIPANGGIL untuk setiap request (termasuk gambar dan file statis), hanya saja langsung keluar di baris pertama. Matcher lebih hemat karena penyaringannya terjadi sebelum fungsi dipanggil. Jadi urutan preferensi: matcher dulu, penyaringan manual hanya untuk logika yang terlalu kompleks untuk diungkapkan sebagai pola path.
Aturan praktisnya: selalu pasang matcher. Middleware tanpa matcher di project besar sama dengan membakar eksekusi Edge setiap hari untuk request yang tidak butuh diperiksa. Kalau ragu pola mana yang benar, uji dengan console.log di dalam middleware lalu buka beberapa URL dan lihat mana yang memicu log.
Tantangan
Matcher hemat
Tambahkan matcher ke middleware-mu agar hanya jalan di /dashboard/* dan /admin/*. Buktikan file statis (mis. /favicon.ico) tidak memicu middleware (cek via console.log).