Nextjs Instrumentation Middleware Auth Pattern Edge Node Runtime Selection
Definisi
Tiga mekanisme yang sering bertemu di aplikasi Next.js production, tapi punya peran berbeda:
- Instrumentation: hook startup yang jalan sekali saat server instance baru mulai, sebelum request pertama dilayani. Dipakai untuk observability (OpenTelemetry, APM), setup global, dan side-effect sekali jalan.
- Middleware auth pattern (guard): middleware yang bertugas memutuskan apakah sebuah request boleh lanjut ke route tujuan, biasanya dengan mengecek keberadaan dan validitas token/session di Edge Runtime.
- Edge vs Node runtime selection: keputusan memilih runtime tempat kode dieksekusi. Middleware selalu Edge; route handler, server component, dan route segment bisa memilih
edgeataunodejs.
Ketiganya beririsan di satu titik: runtime yang dipakai menentukan API apa yang boleh kamu panggil. Salah pilih, gejalanya baru muncul di build atau production, bukan di local.
Kapan instrumentation dipakai
Pakai instrumentation kalau kamu butuh sesuatu berjalan sekali saat startup server, bukan per request.
Contoh yang masuk akal:
- inisialisasi OpenTelemetry / tracing / metrics agent
- koneksi ke APM atau collector (Datadog, Honeycomb, Sentry tracing)
- setup global error handler atau unhandled rejection handler
- warmup: memuat konfigurasi, memvalidasi env, atau menyiapkan pool yang mahal
- registrasi feature flag client atau bootstrap yang hanya perlu dilakukan satu kali
Kapan tidak perlu instrumentation
Jangan pakai instrumentation kalau:
- logika itu harus jalan per request → itu tempatnya middleware atau route handler
- kamu cuma butuh inisialisasi ringan di satu module → cukup module-level code
- setup itu cuma dipakai di satu route → taruh di route handler terkait
Aturan praktis: sekali di startup, bukan per request → instrumentation. Per request → middleware/route.
Cara kerja instrumentation
File instrumentation.ts (atau instrumentation.js) ditaruh di root project, atau di dalam src/ kalau project pakai src/. Next.js memanggil fungsi register() yang diekspor saat server instance boot.
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
// hanya jalan di Node runtime
const { setupObservability } = await import('./lib/observability')
await setupObservability()
}
if (process.env.NEXT_RUNTIME === 'edge') {
// setup ringan khusus edge, misalnya edge cache client
}
}Yang perlu diperhatikan:
register()bolehasync. Next.js menunggunya selesai sebelum mulai menerima request.register()jalan terpisah untuk tiap runtime. Instance Edge dan instance Node masing-masing manggilregister(). Makanya guardNEXT_RUNTIMEpenting.- Di Next.js 15+ instrumentation sudah stabil. Di versi di bawahnya kamu mungkin perlu menyalakan
experimental.instrumentationHook: truedinext.config. - Next.js 15+ juga mendukung pemisahan file langsung:
instrumentation.node.tsdaninstrumentation.edge.ts, masing-masing punyaregister()sendiri tanpa perlu guard manual.
Alur startup:
Middleware auth pattern (guard)
Guard middleware bertugas memutuskan sebelum request sampai ke route: ada token/session yang valid atau tidak. Kalau tidak, redirect ke login (atau kembalikan 401 untuk API).
Kunci supaya guard ini aman di Edge Runtime: verifikasi token secara stateless pakai Web Crypto, bukan library Node. jose adalah pilihan yang tepat karena memakai Web Crypto API sehingga jalan di Edge. jsonwebtoken tidak bisa dipakai di middleware karena butuh Node crypto.
// middleware.ts (root project, atau src/middleware.ts)
import { NextResponse, type NextRequest } from 'next/server'
import { jwtVerify } from 'jose'
const secret = new TextEncoder().encode(process.env.AUTH_SECRET)
async function verify(token: string) {
try {
await jwtVerify(token, secret)
return true
} catch {
return false
}
}
export async function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value
if (!token || !(await verify(token))) {
// API: balas 401. Page: redirect ke login.
if (request.nextUrl.pathname.startsWith('/api/')) {
return NextResponse.json({ error: 'unauthorized' }, { status: 401 })
}
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('from', request.nextUrl.pathname)
return NextResponse.redirect(loginUrl)
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
}Alur keputusan guard:
Rekomendasi desain guard
- Verifikasi signature di edge (murah, stateless). Jangan query database di middleware untuk cek user.
- Tunda lookup data user ke route handler Node. Setelah middleware yakin token valid, route handler yang butuh data user bisa query DB di Node runtime.
- Pisahkan response API vs page. API yang tidak terautentikasi balas
401, bukan redirect, supaya client tidak ikut tersesat. - Exclude target redirect dari matcher biar tidak terjadi loop (misalnya
/loginjangan ke-trigger guard).
Edge vs Node runtime selection
Di mana kamu bisa memilih
- Middleware: selalu Edge. Tidak bisa diubah ke Node.
- Route Handler / Layout / Page (App Router): bisa pilih lewat
export const runtime = 'edge' | 'nodejs'. Defaultnodejs.
// app/api/protected/route.ts
export const runtime = 'nodejs' // bisa diganti 'edge'
export async function GET() {
// ...
}Perbandingan ringkas
| Aspek | Edge Runtime | Node.js Runtime |
|---|---|---|
| Lokasi eksekusi | Dekat user (edge network) | Server/node standar |
| Cold start | Sangat kecil | Lebih besar |
| API Node | Terbatas | Lengkap |
Web Crypto (jose, crypto.subtle) | Didukung | Didukung |
fs, path, native module | Tidak | Ya |
DB driver berat (Prisma, pg) | Tidak | Ya |
| Cocok untuk | guard ringan, geolocation, redirect | query DB, file, library ekosistem Node |
Kapan pakai Edge
- guard middleware (satu-satunya pilihan)
- response ringan yang butuh latency rendah
- logika yang hanya butuh Web Crypto, header, atau geolocation
- tidak butuh akses filesystem atau DB driver Node
Kapan pakai Node
- route handler yang query database
- butuh
fs,path, atau native module - pakai library ekosistem Node yang belum edge-compatible
- butuh proses berat atau stream Node
Alur request ke runtime:
Jebakan runtime mismatch
1. Pakai jsonwebtoken di middleware
jsonwebtoken butuh Node crypto, sedangkan middleware jalan di Edge. Hasilnya: error saat build atau runtime. Solusi: pakai jose (Web Crypto).
2. Import library Node-only ke middleware
bcrypt, pg, prisma, mongoose, atau apa pun yang butuh native module / Node API akan gagal di Edge. Middleware bukan tempatnya. Pindahkan ke route handler Node.
3. Setup DB pool / APM di register() tanpa guard runtime
register() jalan di Edge dan Node. Kalau kamu buka koneksi DB atau init SDK Node-only tanpa mengecek NEXT_RUNTIME, instance Edge akan crash. Selalu guard dengan if (process.env.NEXT_RUNTIME === 'nodejs').
4. Mengira middleware bisa query database langsung
Middleware berjalan di Edge yang tidak punya driver DB Node. Kalau butuh data user, verifikasi token di edge lalu delegasikan lookup ke route handler Node.
5. runtime = 'edge' di route handler tapi pakai fs / native module
Menyetel export const runtime = 'edge' lalu memanggil fs.readFile atau require native akan gagal. Pastikan kode di route tersebut benar-benar edge-safe, atau biarkan default nodejs.
6. Import transitif yang menyeret Node-only
Kadang kamu tidak import bcrypt langsung, tapi sebuah modul shared (misalnya lib/auth) diam-diam mengimpor bcrypt atau pg. Saat middleware mengimpor lib/auth, seluruh chain ikut masuk Edge dan build gagal. Solusi: pisahkan modul edge-safe (lib/auth-edge pakai jose) dari modul Node (lib/auth-node).
7. Middleware terlalu berat
Edge memberi cold start kecil, tapi kalau guard middleware melakukan fetch eksternal, loop, atau komputasi mahal, manfaat latency hilang. Guard harus singkat dan deterministik.
Contoh alur gabungan
Skenario: user buka /dashboard di aplikasi production.
- Request masuk, middleware (Edge) jalan lebih dulu.
- Matcher cocok dengan
/dashboard/:path*. - Middleware baca cookie
token, verifikasi signature pakaijose(Web Crypto). - Token tidak valid → redirect ke
/login(dan/logindi-exclude dari matcher, tidak loop). - Token valid →
NextResponse.next(), request lanjut ke route. - Route handler
/dashboardjalan di Node runtime, query data user dari DB. - Response kembali ke browser.
- Di belakang layar,
instrumentation.register()sudah jalan sekali saat server boot untuk menyiapkan tracing.
Catatan produksi
- Selalu batasi
matchermiddleware sesempit mungkin. Matcher luas = biaya per request naik untuk semua request. - Jangan simpan secret di client.
AUTH_SECRETdibaca di server (middleware jalan di server/edge, bukan browser). joseuntuk verifikasi di edge,jsonwebtokenhanya di Node. Jangan campur.- Untuk auth tingkat produksi, pertimbangkan Auth.js (NextAuth) yang sudah menangani kompatibilitas edge untuk guard middleware.
- Kalau gejala aneh cuma muncul di production: cek runtime yang benar-benar dipakai (Edge vs Node), cek matcher middleware, dan cek apakah modul shared menyeret dependency Node-only.
Referensi terkait
instrumentation.ts: inisialisasi observability dan side-effect sekali jalan saat startup.- Middleware: guard request awal, verifikasi token di Edge, redirect/401.
- Edge Runtime vs Node.js Runtime: pilih berdasarkan kebutuhan API, bukan sekadar "biar cepat".
jose: verifikasi JWT edge-safe lewat Web Crypto.next.config:experimental.instrumentationHook(untuk Next.js di bawah 15).