L
Next.js & SSR Architecture 🚨 Post-Mortem & Incident Runbook • Literally Web ID

Arsitektur Next.js App Router untuk Skala Enterprise: Strategi SSR, ISR, dan Edge Caching

⏱️ 15 menit baca • 📅 20 Oct 2026 • 👁️ 2 views
Arsitektur Next.js App Router untuk Skala Enterprise: Strategi SSR, ISR, dan Edge Caching

1. Alarm Tengah Malam: Saat Layanan Produksi Mulai Tumbang

Pukul 02:14 WIB. Ponsel saya bergetar liar dengan nada dering PagerDuty yang paling memekakkan telinga. Layar menyala menampilkan grafik merah menyala: latensi endpoint utama meroket dari rata-rata 120 milidetik menjadi 8,4 detik, sementara tingkat error rate HTTP 504 Gateway Timeout menembus angka 38%. Di ruang obrolan tim, kepanikan mulai terasa saat tim Customer Support melaporkan bahwa ribuan pengguna di seluruh Asia Tenggara tidak dapat mengakses keranjang belanja mereka. Sebagai Principal Systems Architect yang bertugas on-call minggu ini, saya langsung melompat dari tempat tidur, membuka laptop, dan bersiap menghadapi skenario terburuk.

Sistem kami berjalan di atas arsitektur Next.js App Router yang dideploy ke klaster Node.js, dibungkus di belakang reverse proxy Nginx, dan dilindungi oleh Cloudflare CDN di lapisan terdepan. Konfigurasi ini biasanya sangat tangguh menahan lonjakan trafik flash sale. Namun, malam ini, kombinasi antara strategi Rendering Server-Side (SSR) yang salah kaprah dan kegagalan pengelolaan Edge Caching telah mengubah aplikasi web modern kami menjadi mesin Denial of Service bagi infrastruktur kami sendiri.

[!WARNING] Jangan pernah mengandalkan SSR murni untuk halaman dinamis bersekala besar tanpa lapisan cache yang solid di level CDN. Kegagalan kecil pada manajemen header Cache-Control dapat melumpuhkan seluruh klaster backend dalam hitungan menit.

Sebelum kita masuk ke ruang kendali darurat untuk melihat bagaimana kami membalikkan keadaan dalam waktu kurang dari sepuluh menit, mari kita bedah terlebih dahulu apa yang sebenarnya terjadi di balik layar saat server mulai megap-megap.

2. Jejak Digital: Menelusuri Anomali Log & Lonjakan Metrik

Begitu masuk ke terminal, langkah pertama saya bukanlah langsung melakukan restart—itu adalah dosa besar pertama seorang engineer. Alih-alih bertindak reaktif, saya mulai menyelidiki jejak digital yang ditinggalkan oleh sistem melalui baris-baris log Nginx dan pemantauan metrik CPU Node.js.

# Memeriksa 50 baris terakhir error log Nginx untuk mendeteksi upstream timeout
sudo tail -n 50 /var/log/nginx/error.log | grep "upstream timed out"

Output terminal langsung memberikan petunjuk terang: connect() failed (110: Connection timed out) while connecting to upstream. Saya kemudian membuka dasbor Prometheus dan Grafana untuk melihat penggunaan memori dan CPU pada kontainer aplikasi Next.js. Grafik menunjukkan anomali yang sangat jelas: heap memory melonjak hingga 92% dan memicu Garbage Collection (GC) yang berjalan terus-menerus (GC thrashing), membuat proses Node.js yang single-threaded benar-benar kehabisan siklus CPU.

{
  "timestamp": "2023-10-27T02:12:45Z",
  "level": "error",
  "message": "Worker process exceeded memory threshold",
  "heapUsed": "1.4GB",
  "rss": "1.9GB",
  "activeRequests": 1240
}

Penyebab utamanya mulai terlihat. Permintaan halaman produk tidak lagi dilayani dari cache CDN Cloudflare, melainkan langsung menerobos masuk ke server asal (origin). Setiap permintaan memaksa Next.js untuk menjalankan fungsi Server Component yang melakukan query database berat ke MySQL dan memanggil beberapa microservices internal via HTTP secara bersamaan.

Transisi dari lonjakan trafik normal menuju kolapsnya sistem ini berakar langsung pada bagaimana siklus hidup (lifecycle) App Router dieksekusi di server, sebuah mekanisme yang ternyata salah kami konfigurasi sejak awal.

3. Membongkar Biang Kerok: Akar Masalah yang Sebenarnya

Untuk memahami mengapa sistem ini runtuh, kita harus melihat bagaimana Next.js App Router memproses permintaan. Berbeda dengan Pages Router lama yang lebih prediktif, App Router menggunakan model React Server Components (RSC) di mana komponen dirender di server dan dikirim sebagai aliran data JSON (stream).

Masalah bermula ketika kami menerapkan fungsi fetch dengan opsi cache: 'no-store' pada komponen layout global demi memastikan data pengguna selalu real-time. Akibatnya, halaman utama yang dikunjungi oleh 50.000 pengguna bersamaan tidak lagi bersifat statis. Next.js terpaksa merender ulang seluruh pohon komponen (component tree) dari nol untuk setiap request yang masuk, mengabaikan mekanisme Incremental Static Regeneration (ISR) yang seharusnya meringankan beban server.

Berikut adalah cuplikan kode asli yang menjadi biang kerok bencana malam itu:

// KODE BERBAHAYA: Memaksa dynamic rendering pada komponen global
export default async function GlobalLayout({ children }) {
  // fetch tanpa cache membuat seluruh halaman menjadi dinamis
  const userSession = await fetch('https://api.internal/auth/session', {
    cache: 'no-store' 
  }).then(res => res.json());

  return (
    <html>
      <body>
        <Navbar user={userSession} />
        <main>{children}</main>
      </body>
    </html>
  );
}

[!IMPORTANT] Penggunaan cache: 'no-store' pada level komponen layout akan menular ke seluruh anak komponen di bawahnya (child components), mengubah arsitektur aplikasi dari hibrida (ISR/SSG) menjadi SSR penuh secara tidak sengaja.

Ketika Cloudflare melihat header respons dari server asal yang berisi Cache-Control: private, no-cache, no-store, CDN dengan patuh menolak untuk menyimpan salinan halaman tersebut di Edge. Akibatnya, ribuan hantaman request langsung menghantam origin server tanpa ampun. Memahami celah arsitektural ini menuntun kami langsung ke tindakan darurat penanggulangan.

4. Tindakan Darurat: Menghidupkan Kembali Layanan dalam Hitungan Menit

Dalam situasi krisis seperti ini, tidak ada waktu untuk melakukan refactoring kode besar-besaran. Kami memerlukan intervensi operasional instan untuk mengalihkan beban dan menyelamatkan infrastruktur dari kegagalan total.

Langkah pertama yang saya eksekusi adalah memaksa Cloudflare untuk mengabaikan header dari origin dan menerapkan Edge Caching paksa menggunakan Page Rules / Cache Rules via API, diikuti dengan pembersihan memori pada klaster Node.js dan penyesuaian konfigurasi Nginx sebagai reverse proxy.

Berikut adalah emergency runbook one-liner terminal yang kami jalankan untuk meredakan krisis dalam waktu 60 detik:

# 1. Bersihkan dan restart service PM2/Node.js untuk membuang memori yang bocor
pm2 reload all --update-env

# 2. Perbarui konfigurasi Nginx untuk mengaktifkan micro-caching darurat di proxy
sudo sed -i 's/proxy_cache off;/proxy_cache zone_one; proxy_cache_valid 200 1m;/' /etc/nginx/conf.d/app.conf && sudo systemctl reload nginx

# 3. Purge Cloudflare cache via CLI menggunakan API token darurat
curl -X POST "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/purge_cache" \
     -H "Authorization: Bearer YOUR_API_TOKEN" \
     -H "Content-Type: application/json" \
     --data '{"purge_everything":true}'

Langkah-langkah darurat di atas langsung membuahkan hasil. Grafik latensi turun drastis dari 8,4 detik kembali ke kisaran 150 milidetik, dan error rate menyusut hingga 0%. Namun, memadamkan api saja tidak cukup; kami harus memastikan bara api ini tidak pernah menyala kembali melalui kebijakan retrospektif yang ketat.

5. Kebijakan Retrospektif: Menambal Celah agar Tak Pernah Terulang

Setelah situasi terkendali dan tim dapat bernapas lega, kami mengadakan sesi post-mortem internal untuk merombak total pendekatan arsitektur kami terhadap Next.js App Router. Kami menyadari bahwa ketergantungan pada SSR murni untuk data yang tidak krusial adalah sebuah kesalahan desain fatal.

Kami memutuskan untuk memisahkan data privat (seperti keranjang belanja dan profil pengguna) dari struktur kerangka halaman utama (shell). Kerangka halaman utama kini dikembalikan ke mode Incremental Static Regeneration (ISR) dengan durasi revalidate yang terukur, sementara data privat dimuat secara asinkron di sisi klien menggunakan Client Components dengan SWR atau React Query.

Berikut adalah perbandingan pola arsitektur lama vs baru yang kami terapkan:

Aspek Arsitektur Lama (Bencana) Arsitektur Baru (Defensif)
Rendering Strategy SSR Penuh (Dynamic Rendering) untuk semua halaman Kombinasi SSG/ISR untuk Shell + Client-side Fetch untuk Data User
Edge Caching Dinonaktifkan (no-store di semua tempat) Diaktifkan agresif di Cloudflare (TTL 60 detik untuk konten publik)
Database Load Tinggi (Setiap request menghantam DB) Rendah (Sebagian besar dilayani dari CDN & Redis Cache)
Resistensi Lonjakan Buruk (Rentan terhadap Thundering Herd) Tangguh (Mampu menahan jutaan request via Edge)

Selain mengubah strategi rendering, kami juga menulis ulang kode layout agar memanfaatkan ISR secara maksimal:

// KODE PERBAIKAN: Menggunakan ISR dan memisahkan data privat
export const revalidate = 60; // Cache halaman di server selama 60 detik

export default async function OptimizedLayout({ children }) {
  return (
    <html>
      <body>
        {/* Navbar statis di-cache, data user dimuat via Client Component */}
        <CachedNavbar /> 
        <main>{children}</main>
      </body>
    </html>
  );
}

Transformasi arsitektur ini menuntut tingkat pengawasan yang jauh lebih tinggi agar setiap anomali kecil dapat terdeteksi sebelum berubah menjadi malapetaka. Di sinilah peran sistem pemantauan proaktif menjadi benteng pertahanan terakhir.

6. Pasang Alarm: Otomasi Deteksi Dini untuk Tim On-Call

Pengalaman pahit malam itu mengajarkan kami bahwa menunggu laporan dari pelanggan atau Customer Support adalah bentuk kelalaian sistemik. Kami harus membangun sistem peringatan dini yang cerdas, terotomasi, dan langsung terintegrasi dengan kanal komunikasi tim teknik.

Kami mengonfigurasi ulang aturan Alertmanager di Prometheus untuk memantau metrik kritis secara real-time. Jika latensi rata-rata melebihi 500ms selama dua menit berturut-turut, atau jika error rate HTTP 5xx menembus ambang batas 1%, sistem secara otomatis memicu webhook ke PagerDuty dan mengirimkan notifikasi diagnostik ke kanal Slack darurat.

Berikut adalah konfigurasi aturan Prometheus Alerting (alert.rules.yml) yang kini mengamankan infrastruktur produksi kami:

groups:
  - name: NextjsAppRouterAlerts
    rules:
      - alert: HighServerLatency
        expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) > 0.5
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Latensi tinggi terdeteksi pada Next.js App Router"
          description: "Percentile 95 dari latensi HTTP melampaui 500ms selama lebih dari 2 menit. Segera periksa penggunaan CPU dan beban upstream database."

      - alert: ElevatedErrorRate
        expr: sum(rate(http_requests_total{status=~"5.*\
🏷️ Tags: #Nginx #High Concurrency #Performance #Sysadmin #Web Server