Pada aplikasi Next.js modern, bug yang paling membingungkan sering kali bukan berasal dari komponen UI, melainkan dari alur request: middleware mengubah header, cookie dibaca di lokasi yang berbeda, rewrite memindahkan tujuan request secara diam-diam, atau redirect membuat pengguna terjebak dalam loop. Ketika bug seperti ini muncul di produksi, gejalanya sering tampak sederhana, misalnya halaman selalu kembali ke login, locale salah, atau API tertentu seolah tidak pernah terpanggil. Padahal akar masalahnya biasanya ada pada urutan eksekusi dan asumsi yang keliru tentang apa yang bisa dibaca atau dimodifikasi di setiap tahap.
Artikel ini membahas cara memahami dan men-debug request flow di Next.js 16, khususnya ketika middleware, header, cookie, rewrite, dan redirect terlibat. Fokusnya praktis: memahami urutan eksekusi, mengenali kasus umum, serta menambahkan jejak observabilitas sederhana agar perilaku request lebih mudah dilacak saat menangani bug produksi.
Memahami urutan eksekusi request secara ringkas
Debugging akan jauh lebih mudah jika kita memahami model mentalnya lebih dahulu. Saat browser mengirim request ke aplikasi Next.js, ada beberapa lapisan yang dapat ikut memengaruhi hasil akhir. Detail implementasi bisa berubah antar versi atau konfigurasi deployment, tetapi secara umum urutannya dapat dipahami seperti ini:
- Request masuk dengan URL, method, header, cookie, dan query string.
- Middleware dapat membaca request dan memutuskan apakah request diteruskan, di-rewrite, atau di-redirect.
- Routing menentukan route mana yang akan menangani request setelah perubahan dari middleware diterapkan.
- Handler tujuan berjalan, misalnya halaman App Router, Route Handler, atau endpoint API.
- Response keluar dengan status code, header, dan kemungkinan
Set-Cookie.
Ada beberapa konsekuensi penting dari urutan ini:
- Jika middleware melakukan redirect, route tujuan awal tidak akan dieksekusi.
- Jika middleware melakukan rewrite, URL yang diproses server dapat berbeda dari URL yang dilihat pengguna di browser.
- Jika header atau cookie dimodifikasi di response, perubahan itu belum tentu bisa langsung dibaca oleh kode lain yang sudah telanjur berjalan pada request yang sama.
- Bug sering terjadi karena developer mengira perubahan yang dilakukan pada satu tahap otomatis terlihat pada tahap lain tanpa memperhatikan arah aliran data.
Catatan penting: Middleware adalah tempat yang sangat kuat untuk melakukan kontrol akses, locale negotiation, atau eksperimen berbasis header. Namun karena ia berada di awal request flow, kesalahan kecil dapat berdampak ke seluruh aplikasi.
Perbedaan penting antara rewrite dan redirect
Sebelum masuk ke teknik debugging, pahami dulu dua operasi yang paling sering tertukar:
Redirect
Redirect mengirim respons 3xx ke klien dan meminta browser melakukan request baru ke lokasi lain. Ini berarti ada putaran request tambahan. Browser akan melihat URL baru, histori navigasi bisa berubah, dan loop mudah terjadi bila kondisinya salah.
Rewrite
Rewrite memetakan request ke tujuan internal lain tanpa mengubah URL yang terlihat di browser. Dari sudut pandang pengguna, URL tetap sama, tetapi handler yang dieksekusi bisa berbeda. Ini berguna untuk locale, proxy internal, atau pemetaan route lama ke route baru tanpa memaksa browser pindah URL.
Secara praktis:
- Pilih redirect jika Anda memang ingin klien berpindah ke URL lain, misalnya dari
/loginke/dashboardsetelah autentikasi. - Pilih rewrite jika Anda ingin menyajikan konten dari path lain tanpa mengubah URL publik, misalnya memetakan
/ke/idberdasarkan locale.
Kesalahan umum adalah memakai redirect padahal rewrite lebih tepat. Akibatnya, muncul request tambahan, caching menjadi lebih sulit dipahami, dan kemungkinan loop meningkat.
Contoh middleware: locale dan proteksi area tertentu
Berikut contoh middleware yang cukup realistis: ia menentukan locale default dan memproteksi area /dashboard. Tujuannya bukan membuat logika paling kompleks, melainkan memberi pola yang mudah di-debug.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const PUBLIC_FILE = /\.(.*)$/
const SUPPORTED_LOCALES = ['id', 'en']
const DEFAULT_LOCALE = 'id'
export function middleware(request: NextRequest) {
const { pathname, search } = request.nextUrl
const requestId = crypto.randomUUID()
// Hindari memproses asset dan route internal
if (
pathname.startsWith('/_next') ||
pathname.startsWith('/api') ||
PUBLIC_FILE.test(pathname)
) {
const response = NextResponse.next()
response.headers.set('x-request-id', requestId)
response.headers.set('x-middleware-skip', '1')
return response
}
const localeCookie = request.cookies.get('locale')?.value
const hasLocalePrefix = SUPPORTED_LOCALES.some(
(locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`)
)
// Locale negotiation sederhana
if (!hasLocalePrefix) {
const locale = SUPPORTED_LOCALES.includes(localeCookie || '')
? localeCookie
: DEFAULT_LOCALE
const url = request.nextUrl.clone()
url.pathname = `/${locale}${pathname}`
const response = NextResponse.rewrite(url)
response.headers.set('x-request-id', requestId)
response.headers.set('x-debug-locale', locale)
response.headers.set('x-debug-action', 'rewrite-locale')
return response
}
// Proteksi area dashboard
const token = request.cookies.get('session')?.value
const isDashboard = pathname.startsWith('/id/dashboard') || pathname.startsWith('/en/dashboard')
const isLogin = pathname === '/id/login' || pathname === '/en/login'
if (isDashboard && !token) {
const locale = pathname.split('/')[1]
const loginUrl = request.nextUrl.clone()
loginUrl.pathname = `/${locale}/login`
loginUrl.searchParams.set('from', pathname + search)
const response = NextResponse.redirect(loginUrl)
response.headers.set('x-request-id', requestId)
response.headers.set('x-debug-action', 'redirect-login')
return response
}
if (isLogin && token) {
const locale = pathname.split('/')[1]
const dashboardUrl = request.nextUrl.clone()
dashboardUrl.pathname = `/${locale}/dashboard`
dashboardUrl.search = ''
const response = NextResponse.redirect(dashboardUrl)
response.headers.set('x-request-id', requestId)
response.headers.set('x-debug-action', 'redirect-dashboard')
return response
}
const response = NextResponse.next()
response.headers.set('x-request-id', requestId)
response.headers.set('x-debug-action', 'next')
return response
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}Ada beberapa hal penting dari contoh ini:
- Asset dan route internal dikecualikan supaya middleware tidak mengganggu file statis atau request internal Next.js.
- Header debug ditambahkan untuk membantu pelacakan dari browser DevTools, log reverse proxy, atau observability tool.
- Kondisi proteksi login simetris: dashboard butuh token, login dialihkan ke dashboard jika token sudah ada.
- URL clone digunakan agar perubahan path dan query lebih eksplisit dan aman.
Kasus bug yang paling umum dan cara men-debug-nya
1. Redirect loop
Redirect loop biasanya terjadi ketika kondisi di middleware saling bertentangan atau terlalu luas. Contoh klasiknya:
- User tanpa token diarahkan ke
/login. - Namun route
/loginsendiri juga terkena aturan proteksi yang sama. - Akibatnya request ke
/loginkembali di-redirect ke/loginatau ke lokasi lain yang akhirnya kembali lagi.
Langkah debugging:
- Buka tab Network di browser dan lihat rantai request 3xx.
- Periksa header debug seperti
x-debug-actionataux-request-id. - Pastikan route login, callback auth, dan asset penting tidak ikut terkena kondisi proteksi.
- Verifikasi bahwa kondisi
isDashboarddanisLogintidak tumpang tindih secara tidak sengaja.
Tips penting: buat kondisi yang sespesifik mungkin. Jangan hanya memakai pathname.includes('login') atau pathname.startsWith('/') tanpa batasan jelas, karena mudah menangkap route yang tidak dimaksud.
2. Header tidak terbaca
Masalah lain yang sering membingungkan adalah header yang tampaknya “hilang”. Penyebabnya bisa beberapa hal:
- Header ditambahkan pada response, tetapi Anda mencoba membacanya dari request pada tahap yang berbeda.
- Header diblokir atau tidak diteruskan oleh reverse proxy, CDN, atau platform hosting.
- Nama header berbenturan dengan header standar atau tidak konsisten kapitalisasinya saat diperiksa di alat tertentu.
Prinsip pentingnya: request header datang dari klien ke server, sedangkan response header dikirim server ke klien. Menambahkan response.headers.set('x-debug', '1') tidak berarti kode server lain pada request yang sama otomatis bisa membaca x-debug sebagai request header.
Jika tujuan Anda adalah menandai alur untuk observasi manusia, response header cocok. Jika tujuan Anda adalah mengirim konteks ke service internal lain, desainnya harus mempertimbangkan batas antar-hop dengan jelas.
3. Route tertentu tidak pernah terjangkau
Gejala ini umum ketika middleware menggunakan matcher yang terlalu lebar atau rewrite yang menutupi route lain. Misalnya, semua request tanpa prefix locale di-rewrite ke /id/..., tetapi Anda lupa mengecualikan route tertentu seperti /health, /api, atau callback dari provider OAuth.
Cara men-debug:
- Log atau tandai path asli dan aksi middleware.
- Periksa apakah matcher menangkap route yang seharusnya dikecualikan.
- Uji route sensitif satu per satu:
/api, callback auth, webhook, health check, dan file statis.
Jika menggunakan pihak ketiga seperti identity provider atau payment gateway, callback endpoint sering rusak karena tidak dikecualikan dari middleware locale atau auth.
4. Cookie terasa tidak sinkron
Cookie sering menjadi sumber kebingungan karena ada perbedaan waktu kapan cookie dibaca dan kapan browser menyimpannya. Jika response mengirim Set-Cookie, cookie itu baru tersedia untuk request berikutnya dari browser. Jadi bila Anda berharap middleware berikutnya dalam request yang sama membaca nilai cookie yang baru diset, hasilnya bisa tidak sesuai ekspektasi.
Untuk debugging, perhatikan:
- Apakah cookie memang ada di request saat ini?
- Apakah cookie baru diset di response sebelumnya dan baru akan berlaku pada request berikutnya?
- Apakah atribut cookie seperti
Path,Secure,SameSite, dan domain membuatnya tidak terkirim pada request tertentu?
Teknik pelacakan request yang praktis untuk bug produksi
Di produksi, debugging lewat console.log saja biasanya tidak cukup. Anda perlu jejak yang konsisten agar satu request bisa diikuti dari awal sampai akhir.
Gunakan request ID
Tambahkan ID unik di middleware lalu kirim sebagai response header. Jika Anda juga memiliki logging di backend atau proxy, usahakan ID ini ikut dicatat. Dengan begitu, saat ada laporan “user A selalu di-redirect ke login”, Anda bisa melacak request yang relevan.
const requestId = crypto.randomUUID()
const response = NextResponse.next()
response.headers.set('x-request-id', requestId)
return responseJika aplikasi berada di belakang load balancer atau CDN, pertimbangkan untuk mengadopsi header tracing yang sudah ada di infrastruktur Anda agar korelasi log lebih mudah.
Tambahkan header debug yang aman
Header seperti x-debug-action, x-debug-locale, atau x-debug-auth sangat membantu selama tidak membocorkan data sensitif. Hindari mengirim token, payload cookie, atau alasan internal yang terlalu detail ke klien publik.
Yang aman untuk dibagikan biasanya berupa:
- aksi middleware:
next,rewrite-locale,redirect-login - locale terpilih
- route hasil pemetaan yang tidak sensitif
Log keputusan, bukan hanya nilai mentah
Daripada hanya menulis “token null”, lebih berguna mencatat keputusan final. Misalnya: “path=/id/dashboard, hasToken=false, action=redirect-login”. Log seperti ini mempercepat diagnosis karena langsung menunjukkan hubungan antara input dan keputusan.
Uji dengan curl untuk melihat header dan redirect
Browser kadang menyembunyikan detail karena otomatis mengikuti redirect. Gunakan curl untuk melihat respons mentah:
curl -I http://localhost:3000/dashboard
curl -I -L http://localhost:3000/dashboard
curl -H "Cookie: session=abc123" -I http://localhost:3000/id/dashboardPerintah pertama melihat respons awal, perintah kedua mengikuti redirect, dan perintah ketiga mensimulasikan request dengan cookie tertentu. Ini sangat berguna untuk membuktikan apakah masalah ada pada middleware atau pada state browser pengguna.
Praktik terbaik agar middleware lebih mudah dipelihara
Buat kondisi eksplisit dan sempit
Semakin luas matcher dan aturan path, semakin sulit diprediksi efek sampingnya. Jika hanya /dashboard yang perlu proteksi, jangan menulis aturan yang secara implisit memengaruhi seluruh situs.
Pisahkan logika penentuan status
Jika middleware mulai panjang, pecah logika menjadi fungsi kecil seperti resolveLocale(), isProtectedPath(), dan shouldRedirectToLogin(). Ini membantu pengujian dan mengurangi bug akibat kondisi bercabang yang tidak terbaca.
Jangan andalkan asumsi tersembunyi tentang cookie dan header
Dokumentasikan dengan jelas siapa yang menulis cookie, kapan cookie tersedia, dan header mana yang dipakai hanya untuk observasi versus untuk kontrol alur. Banyak bug produksi berasal dari asumsi yang tidak pernah ditulis.
Siapkan daftar pengecualian route penting
Secara praktis, selalu audit route berikut saat menambahkan middleware baru:
/_next/*/api/*- file statis dan favicon
- callback auth/OAuth
- webhook dari pihak ketiga
- health check dan endpoint internal operasi
Penutup
Debugging middleware, header, cookie, rewrite, dan redirect di Next.js 16 pada dasarnya adalah soal memahami arah aliran request dan titik keputusan. Begitu Anda tahu kapan middleware dijalankan, kapan redirect menghentikan route awal, kapan rewrite mengubah target internal, dan kapan cookie baru benar-benar tersedia, banyak bug yang tadinya tampak misterius menjadi jauh lebih sistematis untuk ditangani.
Dalam praktik produksi, pola yang paling membantu adalah: gunakan kondisi middleware yang sempit, tambahkan request ID, kirim header debug yang aman, dan uji ulang dengan alat yang memperlihatkan respons mentah seperti DevTools dan curl. Dengan pendekatan ini, kasus seperti redirect loop, header yang tidak terbaca, atau route yang seolah tidak pernah tercapai bisa didiagnosis lebih cepat dan dengan risiko regresi yang lebih rendah.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!