Memahami Masalah: Retry Loop dari Middleware Cache
Ketika retry loop muncul di klien yang memanggil API Next.js, penyebabnya sering berasal dari nilai status HTTP yang tidak konsisten atau response yang tidak pernah selesai. Dalam kasus ini, middleware cache menutup response sementara request masih melakukan streaming, sehingga Next.js menulis 429 Too Many Requests di log, klien melihat retry otomatis, dan endpoint tidak pernah menyelesaikan fetch sebagaimana mestinya. Artikel ini langsung menyelesaikan inti masalah tersebut: bagaimana cache middleware yang salah memicu retry loop, tanda-tanda awal, serta solusi praktis.
Gejala Awal di Log dan Klien
Gejala paling jelas terlihat di log server:
- Next.js mencatat
request finished with status 429atau timeout berkala. Cache-Controlheader terlihat tidak konsisten: kadangmax-age=0tapi response tetap ditulis ulang.- Klien mencatat retry otomatis (misalnya dari library Axios atau fetch dengan retry helper), karena response status 429/timeout tidak dianggap sukses.
Retry loop ini dapat menyebabkan beban tambahan dan skenario rate limiting di belakang layar.
Langkah Investigasi
1. Reproduksi Lokal dengan Observability
Jalankan API route di lingkungan development dan pakai tools seperti curl -i untuk melihat header lengkap serta menulis log tambahan dari middleware dan handler. Pastikan log middleware mencatat masuk dan keluar. Simulasikan streaming response agar middleware benar-benar memeriksa apakah response selesai.
2. Gunakan Trace Vercel/Vite
Jika aplikasi berjalan di Vercel, aktifkan trace request di dashboard dan perhatikan apakah middleware cache menulis response body terlebih dahulu sebelum handler selesai. Di Vite/Next.js lokal, gunakan Next.js DevTools atau node --trace-warnings untuk melihat warning yang berkaitan dengan stream/finished.
3. Observasi Header Cache-Control
Periksa apakah middleware menambahkan header dengan nilai yang sama pada setiap retry. Header yang tidak diubah bisa menandakan response sudah muted sebelum handler menyelesaikan streaming.
Analisis Root Cause
Dalam studi kasus ini, middleware cache memicu masalah karena dua alasan utama:
- Middleware menutup response sebelum handler selesai streaming. Karena middleware menyimpan response untuk cache, ia memanggil
res.send()atau menulis header dan body, lalu menyelesaikan response. Handler berikutnya mencoba menulis lagi, tapi sudah tidak bisa, sehingga Next.js menganggap request gagal dan mengirim status 429. - Middleware tidak menunggu asynchronous handler. Middleware diposisikan untuk selesai lebih cepat, namun tidak mengawait stream handler atau tidak meneruskan
await next()yang menyebabkan pipeline terputus.
Akibatnya, klien mendeteksi status error dan memicu retry berkali-kali, menciptakan loop.
Solusi Praktis Beserta Snippet
Kondisi Awal (Sebelum Perbaikan)
export function middleware(req, res, next) {
if (shouldUseCache(req)) {
const cached = cache.get(req.url);
if (cached) {
res.setHeader('Cache-Control', 'max-age=30');
res.send(cached);
return;
}
}
next();
}Middleware di atas menutup response (memanggil res.send) secara sinkron tanpa memperhatikan apakah handler sudah menulis body. Ketika handler mencoba streaming, response sudah selesai.
Perbaikan: Struktur Middleware dengan Async Handling
export async function middleware(req, res, next) {
if (shouldUseCache(req)) {
const cached = cache.get(req.url);
if (cached) {
res.setHeader('Cache-Control', 'max-age=30');
res.setHeader('X-Cache', 'HIT');
res.status(200).send(cached);
return;
}
}
const originalSend = res.send;
const chunks: Buffer[] = [];
res.send = function (body) {
chunks.push(Buffer.isBuffer(body) ? body : Buffer.from(body));
return originalSend.call(this, body);
};
await next();
if (res.statusCode >= 200 && res.statusCode < 300 && res.getHeader('X-Cache') !== 'HIT') {
cache.set(req.url, Buffer.concat(chunks).toString('utf-8'));
}
}
Perhatikan dua aspek penting:
- Middleware sudah async dan
await next(), sehingga handler bebas menyelesaikan streaming. - Cache hanya disimpan setelah handler sukses menulis response, dan klien menerima status 2xx.
Retry-safe Status dan Observasi Tambahan
Jika middleware menyertakan Retry-After, pastikan status yang dikirim adalah 409 atau 503 dengan body yang jelas. Namun dalam kasus ini, solusi utama adalah memastikan middleware tidak menyelesaikan response terlalu dini.
Checklist Verifikasi Setelah Perbaikan
- Log: Status 200 tercatat tanpa warning 429.
- Header:
Cache-Controlkonsisten,X-Cachemenandai langsung HIT atau MISS. - Retry: Klien tidak lagi melakukan retry loop karena response selesai normal.
- Observability: Trace Vercel/Vite menunjukkan pipeline middleware → handler selesai tertib.
- Pemantauan: Tambahkan metric latency/responsiveness untuk mengecek jika middleware menaruh blok asynchronous.
Pelajaran untuk Developer
- Jangan tutup response di middleware sebelum pipeline selesai. Middleware cache harus menunggu handler, atau hanya menangani cache hanya jika response sudah final.
- Gunakan status retry-safe jika benar-benar perlu waktu tunggu, tetapi hindari mengirim status 429 tanpa alasan.
- Observability penting: gunakan header tambahan dan tracing untuk memastikan middleware tidak memecah aliran request.
- Perhatikan streaming: kalau handler membalas dengan streaming, middleware tidak boleh menimpa response body.
Dengan pendekatan di atas, retry loop karena middleware caching bisa dihilangkan tanpa mengorbankan performa cache dan tetap memberi klien respons yang konsisten.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!