Skip to main content
BlogWeb Dev

Cara Membuat Middleware Next.js: Matcher, Redirect, Rewrite

Panduan pemula cara membuat middleware Next.js: middleware.ts, matcher, redirect, rewrite, header, dan cara mengujinya dengan curl, plus error umumnya.

Matthews Wong

Software Engineer

Oktober 202610 menit baca
Cara Membuat Middleware Next.js: Matcher, Redirect, Rewrite

Ringkasan Utama

Cara membuat middleware Next.js: buat file middleware.ts di root proyek, sejajar folder app, lalu ekspor fungsi yang menerima request dan mengembalikan NextResponse. Batasi dengan matcher agar tidak jalan di file statis, pakai redirect untuk memindahkan URL, rewrite untuk menayangkan halaman lain di URL yang sama, dan uji semuanya dengan curl.

Kamu menambahkan halaman /dashboard, lalu sadar siapa pun yang mengetik alamatnya langsung bisa membukanya. Kamu ingin satu tempat yang memeriksa request sebelum halaman dirender. Begitu file middleware dibuat, ada tiga hal yang sering terjadi: tidak ada yang terjadi sama sekali, CSS dan gambar ikut terblokir, atau browser berputar-putar sampai muncul ERR_TOO_MANY_REDIRECTS.

Panduan ini membangun satu file middleware kecil dari nol di Next.js 15 (App Router): header kustom, matcher, redirect bersyarat, rewrite, dan cara mengujinya dengan curl. Setiap klaim tentang API sudah dicocokkan dengan dokumentasi Next.js yang tercantum di bagian sumber, dan perilaku matcher serta NextResponse saya jalankan langsung di Next.js 15.5.7. Pola matcher yang dipakai sama dengan middleware.ts di situs ini.

Mau paham konsepnya lewat simulasi?

Panduan ini fokus pada langkah dan error. Lesson gratis kami menjelaskan urutan eksekusi, content negotiation lewat header Accept, dan kenapa Vary penting, lengkap dengan kuis: Tutorial Middleware Next.js: Matcher, Rewrite, dan Accept. Selesaikan panduan ini dulu supaya kamu punya middleware yang berjalan, lalu lanjut ke lesson untuk melihat bagaimana situs ini memakainya.

Yang kamu butuhkan

Semua bisa dikerjakan di laptop, tanpa server dan tanpa biaya tambahan.

  • Proyek Next.js 15 dengan App Router (folder app), misalnya hasil create-next-app. Panduan ini memakai TypeScript.
  • Dev server yang berjalan lewat npm run dev, biasanya di http://localhost:3000.
  • curl di terminal. Kita menguji tanpa membuka browser, karena middleware hanya soal status, header, dan lokasi.
  • Tiga halaman kosong supaya tujuan redirect dan rewrite ada: app/login/page.tsx, app/dashboard/page.tsx, dan app/campaign/page.tsx.

Langkah 1: Buat middleware.ts pertama dan pasang satu header

Middleware adalah satu file bernama middleware.ts di root proyek, sejajar dengan folder app (atau di dalam src jika kamu memakainya). Isinya satu fungsi yang menerima request, dan boleh me-rewrite, me-redirect, mengubah header, atau langsung menjawab. NextResponse.next() berarti lanjutkan seperti biasa. Kita pasang satu header sebagai bukti bahwa fungsinya berjalan.

// middleware.ts
// Wrong: app/middleware.ts or app/lib/middleware.ts. Next.js never loads it.
// Right: the project root, at the same level as the app folder (or inside src/).
import { NextResponse, type NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const response = NextResponse.next(); // continue as normal
  response.headers.set("x-hello-from-middleware", "hello");
  return response;
}

// In a second terminal, while "npm run dev" is running:
//   curl -I http://localhost:3000/

Output yang diharapkan: Respons berstatus 200 dan memuat baris x-hello-from-middleware: hello. Jika baris itu tidak ada, middleware belum terbaca: lihat baris pertama di tabel error.

Catatan versi: panduan ini memakai Next.js 15, tempat file-nya bernama middleware.ts. Di Next.js 16 konvensi itu diganti nama menjadi proxy dengan konsep yang sama; langkah migrasinya ada di Migrasi middleware.ts ke proxy.ts di Next.js 16. Dokumentasi juga menyebut fitur ini sebaiknya jadi pilihan terakhir, jadi pakai ketika next.config atau sebuah route tidak cukup.

File middleware.ts yang ditaruh di dalam folder app tidak akan pernah dijalankan. Letaknya harus sejajar dengan app, dan satu file hanya boleh mengekspor satu fungsi middleware.

Langkah 2: Batasi dengan matcher supaya file statis tidak ikut

Tanpa matcher, middleware berjalan pada setiap request, termasuk file statis, optimasi gambar, dan isi folder public. Begitu kamu menambah redirect, CSS dan gambar ikut terpental. Matcher adalah export config yang menentukan path mana yang boleh sampai ke fungsi.

// Add to the bottom of middleware.ts

export const config = {
  // Wrong: only api and Next.js internals are excluded, so /next.svg and
  //        every other file in public/ still goes through the function.
  // matcher: ["/((?!api|_next/static|_next/image).*)"],

  // Right: also exclude any path that contains a dot (a file extension).
  // The doubled backslash is one escaped dot inside a JavaScript string.
  matcher: ["/((?!api|_next|.*\\..*).*)"],

  // Alternative: list only the paths that need the function.
  // matcher: ["/dashboard/:path*", "/promo"],
};

// Matcher values must be literal constants. A variable is ignored,
// because Next.js reads this export at build time.

//   curl -I http://localhost:3000/api/hello
//   curl -I http://localhost:3000/next.svg
//   curl -I http://localhost:3000/

Pola di bawah adalah negative lookahead: cocokkan semua path kecuali yang diawali api atau _next, dan kecuali path yang memuat titik. Klausa titik itulah yang menyingkirkan file seperti next.svg, robots.txt, dan sitemap.xml. Dua aturan dari dokumentasi: source harus diawali slash, dan nilai matcher harus konstanta karena dianalisis saat build, sehingga variabel diabaikan.

Output yang diharapkan: curl -I ke /api/hello dan ke /next.svg tidak lagi memuat x-hello-from-middleware, sedangkan halaman biasa seperti / tetap memuatnya.

Langkah 3: Redirect halaman yang butuh login

Redirect mengirim klien ke URL lain: browser berpindah dan alamatnya berubah. Kita redirect /dashboard ke /login bila cookie bernama session tidak ada, dan menitipkan alamat asal di query from supaya halaman login bisa mengembalikan pengguna. Contoh di dokumentasi membangun tujuannya dengan objek URL dari request.url.

// middleware.ts  (replace the function body from step 1)
export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // Wrong: if (!request.cookies.has("session")) redirect for EVERY path.
  //        /login matches too, so it redirects to /login forever.
  // Right: only guard the paths that need a login.
  if (pathname.startsWith("/dashboard") && !request.cookies.has("session")) {
    const loginUrl = new URL("/login", request.url);
    loginUrl.searchParams.set("from", pathname); // remember where they were going
    return NextResponse.redirect(loginUrl); // 307 by default
  }

  const response = NextResponse.next();
  response.headers.set("x-hello-from-middleware", "hello");
  return response;
}

//   curl -i http://localhost:3000/dashboard
//   curl -i -H "Cookie: session=abc" http://localhost:3000/dashboard

Kode ini sengaja hanya mengecek path yang diawali /dashboard. Itu mencegah loop: kalau semua path tanpa cookie diredirect ke /login, maka /login sendiri ikut diredirect ke /login tanpa akhir, dan browser menyerah dengan ERR_TOO_MANY_REDIRECTS. Status yang dipakai NextResponse.redirect secara default adalah 307, yaitu redirect sementara yang mempertahankan method dan body request.

Output yang diharapkan: Tanpa cookie, curl -i menampilkan HTTP 307 dan baris location yang menuju http://localhost:3000/login?from=%2Fdashboard. Dengan cookie session, tidak ada lagi 307 ke /login.

Cek cookie seperti ini hanya optimistic: cookie palsu bernama session juga lolos. Dokumentasi autentikasi Next.js menyarankan middleware hanya membaca cookie tanpa query database, dan menegaskan bahwa middleware tidak boleh menjadi satu-satunya pertahanan. Pemeriksaan sebenarnya dilakukan sedekat mungkin dengan data.

Langkah 4: Rewrite untuk menayangkan halaman lain di URL yang sama

Rewrite menampilkan isi sebuah URL sebagai pengganti URL yang diminta, dan alamat di browser tidak berubah. Di contoh ini, /promo menayangkan halaman /campaign. Berikut file middleware.ts lengkap sampai langkah ini.

// middleware.ts  (the whole file so far)
import { NextResponse, type NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  if (pathname.startsWith("/dashboard") && !request.cookies.has("session")) {
    const loginUrl = new URL("/login", request.url);
    loginUrl.searchParams.set("from", pathname);
    return NextResponse.redirect(loginUrl);
  }

  // Wrong: NextResponse.redirect(...) here. The address bar would change to /campaign.
  // Right: rewrite. The visitor keeps /promo and gets the /campaign page.
  if (pathname === "/promo") {
    return NextResponse.rewrite(new URL("/campaign", request.url));
  }

  const response = NextResponse.next();
  response.headers.set("x-hello-from-middleware", "hello");
  return response;
}

export const config = {
  matcher: ["/((?!api|_next|.*\\..*).*)"],
};

//   curl -i http://localhost:3000/promo

Aturan memilihnya sederhana. Pakai redirect bila alamat memang harus berpindah, misalnya halaman dipindah atau pengguna perlu login. Pakai rewrite bila alamat harus tetap tetapi isinya berasal dari tempat lain. Situs ini memakai rewrite di middleware.ts untuk melayani versi Markdown di URL halaman yang sama bagi agen yang meminta text/markdown, tanpa lompatan redirect.

Output yang diharapkan: curl -i ke /promo menghasilkan HTTP 200 tanpa baris location, dan isinya halaman campaign. Bandingkan dengan /dashboard di langkah 3 yang menghasilkan 307.

Langkah 5: Titipkan header ke halaman dengan arah yang benar

Kadang middleware perlu menitipkan informasi ke halaman atau route di belakangnya. Ada dua cara yang mirip tetapi arahnya berlawanan. NextResponse.next dengan opsi request lalu headers meneruskan header ke atas, yaitu ke halaman, route, atau server action, dan tidak mengirimnya ke klien. NextResponse.next dengan opsi headers langsung mengirim header ke klien, dan dokumentasi menandainya sebagai praktik yang tidak baik karena bisa menimpa header seperti Content-Type yang dibutuhkan framework.

// Replace the last three lines of the function (NextResponse.next ... return response)
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-version", "123");

// Right: forward upstream. The page or route sees x-version; the client does not.
const response = NextResponse.next({
  request: { headers: requestHeaders },
});

// Wrong: NextResponse.next({ headers: requestHeaders })
// That sends them to the CLIENT, and can override headers such as Content-Type.

// A header meant for the client is set on the response itself.
response.headers.set("x-hello-from-middleware", "hello");
return response;

//   curl -I http://localhost:3000/

Pemakaian nyata pola ini adalah menitipkan nonce untuk Content Security Policy; ceritanya ada di Content Security Policy di Next.js dengan nonce. Jangan menyalin semua header request. Buat subset dengan allow-list dan buang authorization serta cookie. Hindari header besar karena bisa memicu error 431 Request Header Fields Too Large.

Output yang diharapkan: curl -I ke sebuah halaman tidak menampilkan x-version di respons, karena header itu hanya diteruskan ke atas. Header x-hello-from-middleware tetap muncul karena kita memasangnya langsung di respons.

Langkah 6: Uji semuanya dengan curl

Hasil middleware hanyalah status, header, dan Location, jadi curl sudah cukup untuk mengujinya. Simpan perintah berikut sebagai skrip kecil dan jalankan setiap kali kamu mengubah matcher atau logika. Satu loop menguji lima jenis path sekaligus: halaman biasa, API, file statis, halaman yang diredirect, dan halaman yang di-rewrite.

# check-middleware.sh  (run while "npm run dev" is up)
# -s silent, -o /dev/null drop the body, -D - print only the response headers.
for p in / /api/hello /next.svg /dashboard /promo; do
  echo "== $p"
  curl -s -o /dev/null -D - "http://localhost:3000$p" | grep -iE "^(HTTP|location|x-hello)"
done

# The passing branch of the redirect:
curl -s -o /dev/null -D - -H "Cookie: session=abc" http://localhost:3000/dashboard | grep -iE "^(HTTP|location)"

Setelah itu ulangi untuk /dashboard dengan cookie, supaya cabang yang lolos juga terbukti.

Output yang diharapkan: Halaman / menghasilkan 200 dan x-hello. /api/hello dan /next.svg tidak memuat x-hello (statusnya mengikuti route atau file itu sendiri). /dashboard menghasilkan 307 dengan location ke /login?from=%2Fdashboard. /promo menghasilkan 200 tanpa location. Dengan cookie, /dashboard tidak lagi 307.

Ingin tes otomatis? Next.js menyediakan helper eksperimental di next/experimental/testing/server: unstable_doesMiddlewareMatch untuk memastikan matcher cocok atau tidak pada sebuah URL, serta isRewrite, getRewrittenUrl, dan getRedirectUrl untuk memeriksa hasilnya. Namanya berawalan unstable karena masih eksperimental, dan di dokumentasi Next.js 16 fungsi pertama bernama unstable_doesProxyMatch.

Error umum dan cara memperbaikinya

Delapan masalah yang paling sering membuat pemula buntu saat pertama kali membuat middleware di Next.js.

GejalaPenyebabPerbaikan
Middleware tidak jalan: header x-hello-from-middleware tidak munculFile berada di dalam folder app, namanya salah, atau fungsinya tidak diekspor.Pindahkan ke root proyek sejajar app (atau ke src), beri nama middleware.ts, dan ekspor fungsi bernama middleware atau default.
CSS, gambar, atau file di public ikut kena redirect atau rewriteTidak ada matcher, atau polanya belum mengecualikan path berekstensi (misalnya hanya api dan _next/static).Tambahkan config dengan negative lookahead yang mengecualikan api, _next, dan path yang berisi titik.
Browser menampilkan ERR_TOO_MANY_REDIRECTSTujuan redirect (misalnya /login) juga kena aturan redirect yang sama.Batasi aturan ke path yang memang butuh (pathname.startsWith) atau persempit matcher supaya halaman tujuan tidak ikut.
Matcher seperti diabaikanNilainya variabel atau hasil perhitungan, padahal matcher dianalisis saat build, atau source tidak diawali slash.Tulis matcher sebagai string konstanta literal yang diawali slash.
Alamat di browser berubah padahal ingin tetapMemakai NextResponse.redirect.Pakai NextResponse.rewrite supaya isi berasal dari URL lain tetapi alamat tetap.
Header dari middleware tidak terbaca di halaman, atau malah muncul di respons ke klienArahnya tertukar: opsi headers pada next mengirim ke klien, bukan ke halaman.Pakai next dengan opsi request berisi headers untuk meneruskan ke atas, dan response.headers.set untuk header ke klien.
Halaman dilindungi, tetapi Server Action atau path yang dikecualikan matcher masih bisa diaksesMatcher yang mengecualikan sebuah path juga melewatkan Server Function di path itu, dan cek cookie hanya optimistic.Verifikasi autentikasi dan otorisasi di dalam setiap Server Action dan Route Handler, sedekat mungkin dengan data.
Setelah pindah ke Next.js 16, dokumentasi menyebut konvensi middleware deprecatedKonvensi file middleware diganti nama menjadi proxy.Jalankan codemod resmi middleware-to-proxy lewat npx @next/codemod@canary di folder proyek. Ia mengganti nama file dan nama fungsinya.

Checklist sebelum deploy

  • File bernama middleware.ts dan sejajar folder app (di Next.js 16 menjadi proxy.ts).
  • Matcher memakai konstanta dan mengecualikan api, _next, serta path berekstensi.
  • Redirect hanya untuk path yang butuh, dan halaman tujuannya tidak ikut diredirect.
  • Cek cookie dianggap optimistic; otorisasi sebenarnya ada di dekat data dan di setiap Server Action.
  • Header ke halaman diteruskan lewat opsi request, dan tidak semua header request disalin.
  • Semua kasus sudah diuji dengan curl: halaman biasa, API, file statis, redirect, dan rewrite.

Belajar lebih dalam

Sekarang kamu punya middleware.ts yang menambah header, memilih request lewat matcher, me-redirect, me-rewrite, dan bisa diuji dengan curl. Untuk memahami kenapa urutan eksekusinya begitu, bagaimana situs ini membaca header Accept untuk melayani Markdown di URL yang sama, dan kenapa Vary: Accept penting, lanjutkan ke lesson gratis berikut.

Tutorial Middleware Next.js: Matcher, Rewrite, dan Accept

Lesson itu membedah middleware.ts situs ini baris demi baris, dengan simulasi interaktif dan kuis di akhir.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa itu middleware di Next.js dan kapan sebaiknya dipakai?

Middleware adalah fungsi di file middleware.ts yang berjalan di server sebelum request diselesaikan, dan bisa me-rewrite, me-redirect, mengubah header, atau langsung menjawab. Cocok untuk keputusan lintas halaman seperti redirect, logging, dan pengecekan login yang optimistic. Dokumentasi Next.js menyebutnya sebagai pilihan terakhir, jadi coba next.config atau sebuah route dulu.

Kenapa middleware.ts saya tidak jalan di Next.js?

Penyebab paling sering adalah lokasinya: file harus ada di root proyek sejajar folder app, atau di dalam src jika kamu memakainya, bukan di dalam app. Periksa juga nama file dan pastikan kamu mengekspor fungsi bernama middleware atau default export. Kalau ada matcher, pastikan URL yang kamu uji memang cocok dengan polanya.

Apa bedanya rewrite dan redirect di middleware Next.js?

Redirect mengirim klien ke URL lain sehingga alamat di browser berubah, dan NextResponse.redirect memakai status 307 secara default. Rewrite menampilkan isi URL lain sementara alamatnya tetap sama. Pakai redirect saat alamat harus berpindah, dan rewrite saat hanya isinya yang berasal dari tempat lain.

Apakah middleware cukup untuk melindungi halaman dashboard yang butuh login?

Tidak cukup. Cek cookie di middleware hanya optimistic, karena cookie palsu dengan nama yang benar tetap lolos. Dokumentasi autentikasi Next.js menyatakan middleware tidak boleh menjadi satu-satunya pertahanan, dan pemeriksaan sebenarnya harus dekat dengan data, termasuk di dalam setiap Server Action dan Route Handler.

Apakah middleware sudah diganti menjadi proxy di Next.js 16?

Ya, dokumentasi Next.js 16 menyebut konvensi file middleware deprecated dan diganti nama menjadi proxy, sehingga file menjadi proxy.ts dan fungsinya bernama proxy. Konsepnya, termasuk matcher, redirect, dan rewrite, tetap sama. Codemod middleware-to-proxy bisa mengganti nama file dan fungsinya untukmu.