nextjsmiddlewareMahir3 mnt baca

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

ts
// 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:

ts
// 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 /dashboard sendiri dan /dashboard/pengaturan/profil.
  • /blog/:slug : tepat satu segmen dinamis, cocok untuk /blog/abc tapi tidak untuk /blog/abc/komentar.
  • /blog/:slug* : satu segmen atau lebih, jadi /blog/abc/komentar ikut 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:

ts
// 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).