Debugging Nuxt.js SSR Middleware: Gejala dan Tujuan
Gejala awal yang memicu investigasi adalah lonjakan penggunaan memori dan latency saat render server-side API route Nuxt.js, yang terjadi meskipun beban permintaan tidak meningkat. Debugging Nuxt.js SSR Middleware diarahkan untuk menemukan akar penyebab aliran data middleware yang terus mempertahankan referensi global dan menumpuk ketika request bergantian.
Artikel ini menjawab langsung masalah tersebut dengan panduan reproduksi, pengamatan V8 heap, penjelasan akar masalah, dan langkah perbaikan konkret agar middleware menjadi stateless serta cache terbatas ruang lingkupnya.
Langkah Reproduksi dan Observasi Awal
Reproduksi masalah dilakukan pada lingkungan staging dengan:
- Menjalankan Nuxt.js SSR server dengan route API, misalnya
/api/orders, yang dipanggil berkali-kali oleh skrip beban ringan. - Mengawasi memory RSS proses Node.js serta latency response via
curlatau alat load testing sederhana. - Mencatat bahwa memory terus naik walau request rate stabil, dan latency response bertambah seiring request ke-n.
Observasi awal menyoroti bahwa masalah muncul saat middleware dijalankan untuk setiap request, sehingga stack middleware menjadi kandidat utama.
Pemahaman Profiling dan Heap
Profiling menggunakan node --inspect dan Chrome DevTools memperlihatkan peningkatan heap dominan pada objek middleware. Snapshot V8 heap menunjukkan ratusan gorup objek yang disimpan dalam cache global, bukan dibersihkan setelah response.
Perhatikan dua metrik penting:
- Heap size: Terus naik dalam tiap snapshot tanpa turun setelah GC.
- Retained size: Objek middleware yang menyimpan referensi ke response/callback sebelumnya tetap hidup karena cache global yang tidak di-reset.
Profiling timeline mengkonfirmasi bahwa middleware async menyelesaikan promise-nya, tetapi objek terkait tidak pernah dilepas.
Root Cause: Middleware Async dan Cache Global
Akar masalah adalah kombinasi middleware yang menginstansiasi context ~per-request~ dan sebuah cache global yang menyimpan promise/recorder untuk hasil API. Contoh pola bermasalah:
const responseCache = new Map();
export default defineNuxtRouteMiddleware(async (to, from) => {
if (!responseCache.has('orders')) {
const data = await useFetchOrders();
responseCache.set('orders', data);
}
return responseCache.get('orders');
});Di atas, responseCache global menyimpan reference ke data request dan ke objek middleware, membuat GC tidak membuang heap walau middleware sudah selesai. Selain itu, useFetchOrders() adalah composable yang menangani state internal, sehingga middleware menjadi stateful.
Langkah Perbaikan: Stateless Composable dan Pembatasan Cache
Perbaikan fokus pada dua prinsip: menjadikan middleware stateless dan membatasi scope cache hanya untuk data yang benar-benar safe untuk disimpan antar request.
Contoh refactor:
export default defineNuxtRouteMiddleware(async (to, from) => {
const orders = await fetchOrdersFromApi({ locale: to.params.locale });
return orders;
});
const fetchOrdersFromApi = async (options) => {
const cacheKey = `orders:${options.locale}`;
if (shouldUseCache(cacheKey)) {
return getCachedData(cacheKey);
}
const response = await $fetch('/api/internal/orders', { params: options });
storeCache(cacheKey, response, 60); // kedaluwarsa 60 detik
return response;
};Perubahan utama:
- Middleware stateless: tidak menyimpan state internal dan selalu mengembalikan hasil fresh.
- Cache scoped: cache dikelola oleh helper terpisah dengan kadaluarsa, sehingga referensi jangka panjang tidak menumpuk.
- Composable deep clean: fungsi
fetchOrdersFromApihanya beroperasi pada data request dan me-release cache setelah TTL.
Cache helper sebaiknya dijadikan module terpisah yang bisa memanfaatkan WeakRef atau mekanisme kadaluarsa eksplisit, bukan Map global tanpa batas.
Tips Observabilitas dan Mitigasi Risiko
- Heap monitoring: gunakan agent observability (seperti Datadog, Prometheus+Node exporter, atau custom metrics) untuk memantau trend heap size dan frequency GC.
- Trace middleware: tambahkan logging terstruktur untuk lifecycle middleware sehingga Anda tahu berapa kali setiap middleware dieksekusi dan berapa lama promise-nya.
- Limit cache scope: kalau harus cache, batasi scope objek ke nilai primitif dan set TTL otomatis. Hindari menyimpan referensi lengkap ke response stream.
- Reduce async state: hindari penggunaan
asynccomposable dengan dependency internal yang tidak di-reset, sebab Promise-resolve-nya masih mengikat state hingga selesai GC.
Dengan observability memadai, Anda bisa mendeteksi kembali kebocoran lebih cepat sebelum impact ke produksi membesar.
Validasi Pasca Perbaikan
Setelah refactor selesai, jalankan kembali skenario reproduksi dan bandingkan metrics:
- Memori stabil: heap size tidak lagi meningkat signifikan setelah load steady.
- Latency konsisten: response time tidak mengalami degradasi walau middleware tetap dipanggil.
- GC release: snapshot heap menunjukkan jumlah objek middleware turun kembali setelah tiap request.
Tambahkan automated test atau synthetic check yang memanggil route API dan memvalidasi bahwa cache tidak berubah-ubah secara internal. Pastikan monitoring heap dan alert memory leak tetap aktif agar regresi segera kelihatan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!