# Optimasi Performa Next.js App Router dengan Incremental Static Regeneration

> Diterbitkan oleh Literally Web ID pada 20 Oct 2026
> Sumber: https://literally.web.id/articles/optimasi-performa-nextjs-app-router-dengan-incremental-static-regeneration

---

Pernahkah kamu berada di situasi jam 2 pagi, ketika pagerduty berdering kencang karena database PostgreSQL di production mendadak mengalami connection saturation? CPU melonjak ke 99%, sementara ribuan user bersamaan menekan tombol refresh pada halaman e-commerce yang sedang flash sale. Ini adalah mimpi buruk klasik dari arsitektur Server-Side Rendering (SSR) murni. Setiap request datang, server Node.js harus berjibaku melakukan query ke database, merender komponen React menjadi string, lalu mengirimkannya kembali ke client. Bayangkan beban ini dikali puluhan ribu concurrent users. Di titik inilah kita sadar, mengandalkan kekuatan hardware semata tanpa membenahi arsitektur rendering adalah jalan pintas menuju kehancuran sistem.

Perjalanan kita dimulai dari pengalaman pahit tersebut, di mana kita harus memutar otak mencari cara agar aplikasi Next.js tetap responsif layaknya situs statis, namun tetap dinamis secara data tanpa mengorbankan kesehatan infrastruktur backend. Di sinilah jurus penyelamat bernama Incremental Static Regeneration (ISR) masuk ke dalam skenario permainan. Sebelum kita menyelami baris kode, mari kita bedah terlebih dahulu bagaimana mekanisme di balik layar membuat transisi dari SSR melelahkan menjadi ISR yang anggun dan mematikan dalam hal kecepatan.

Secara konseptual, arsitektur web modern terjebak dalam dilema abadi: memilih antara kecepatan statis (SSG) atau kekinian data (SSR). SSG membuat web secepat kilat karena di-build sekali di awal, tapi begitu ada perubahan harga produk, kita harus merender ulang seluruh situs yang berisi jutaan halaman. Sebaliknya, SSR selalu menyajikan data paling fresh, namun mengorbankan latensi dan mencekik database. ISR hadir sebagai jembatan penyeberangan yang elegan. Konsepnya sederhana namun brilian: halaman disajikan secara statis dari cache CDN terdekat, sementara secara diam-diam di background, Next.js melakukan re-generation saat ada request baru yang masuk setelah batas waktu tertentu (revalidate).

> [!TIP]
> Anggaplah ISR seperti pelayan restoran cepat saji yang menyiapkan burger di rak pajangan agar customer bisa langsung mengambilnya dalam hitungan milidetik, sementara koki di dapur diam-diam meracik porsi baru begitu burger di rak mulai mendingin.

Namun, peralihan ini tentu menuntut kompromi arsitektural. Kita harus rela melepaskan kontrol instan terhadap data real-time murni untuk halaman-halaman tertentu, menggantinya dengan konsistensi eventual (eventual consistency). Pertanyaannya, apakah semua halaman cocok diperlakukan sama? Tentu tidak. Halaman keranjang belanja (cart) atau dashboard privat wajib menggunakan SSR atau Client-Side Fetching, sementara halaman detail produk, blog post, dan kategori katalog adalah ladang subur bagi ISR. Memahami batas garis demarkasi ini adalah kunci utama agar sistem tidak salah kaprah dalam mengeksekusi caching.

Sebelum mulai meracik baris kode, mari kita siapkan fondasi infrastruktur pendukungnya. Di level server, kita tidak hanya mengandalkan proses Next.js yang berjalan di atas PM2 atau Docker, tetapi juga menempatkan reverse proxy Nginx di depan untuk menangani SSL termination, HTTP/2 multiplexing, serta rate limiting dasar guna menangkis serangan DDoS amatiran. Di layer terluar, Cloudflare bertindak sebagai benteng pertahanan Edge CDN dan WAF.

Berikut adalah konfigurasi blok server Nginx yang biasa kita gunakan di production untuk mengarahkan traffic ke aplikasi Next.js yang berjalan pada port 3000:

```nginx
# Konfigurasi Nginx untuk Reverse Proxy Next.js dengan optimasi static asset
upstream nextjs_upstream {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Cache static Next.js assets langsung di Nginx untuk meringankan beban Node.js
    location /_next/static/ {
        alias /var/www/my-app/.next/static/;
        expires 365d;
        access_log off;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    location / {
        proxy_pass http://nextjs_upstream;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

Setelah fondasi server siap, saatnya kita masuk ke implementasi inti pada sisi kode aplikasi Next.js App Router. Bayangkan kita sedang membangun platform e-commerce skala besar di mana halaman produk harus memuat data dari database MySQL melalui ORM seperti Prisma, dan gambar disimpan di AWS S3. Kita menggunakan fungsi `generateStaticParams` untuk melakukan pre-render produk terlaris, dan mengatur opsi `revalidate` pada level segmen rute.

Berikut adalah contoh implementasi file `app/products/[slug]/page.tsx` yang menerapkan ISR secara presisi:

```typescript
import { notFound } from 'next/navigation';
import { db } from '@/lib/db';

// Mengatur durasi revalidation dalam detik (misal: tiap 60 detik)
export const revalidate = 60;

// Menentukan parameter apa saja yang di-generate secara statis saat build time
export async function generateStaticParams() {
  const topProducts = await db.product.findMany({
    take: 50,
    select: { slug: true },
    orderBy: { salesCount: 'desc' },
  });

  return topProducts.map((product) => ({
    slug: product.slug,
  }));
}

async function getProductData(slug: string) {
  const product = await db.product.findUnique({
    where: { slug },
    include: { category: true, variants: true },
  });

  if (!product) return null;
  return product;
}

export default async function ProductPage({ params }: { params: { slug: string } }) {
  const product = await getProductData(params.slug);

  if (!product) {
    notFound();
  }

  return (
    <main className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-4">{product.name}</h1>
      <p className="text-xl text-green-600 font-semibold mb-6">
        Rp {product.price.toLocaleString('id-ID')}
      </p>
      <div className="prose max-w-none">
        <p>{product.description}</p>
      </div>
    </main>
  );
}
```

Ada kalanya baris kode terlihat sangat rapi di local development, namun menyimpan jebakan mematikan di production. Salah satu kesalahan paling klasik yang sering dilakukan engineer pemula adalah salah mengkonfigurasi header cache pada CDN seperti Cloudflare atau salah memahami perilaku `fetch` caching otomatis di Next.js App Router yang secara default menggunakan `force-cache`.

> [!WARNING]
> Jangan pernah mencampuradukkan `no-store` pada fetch request di dalam halaman yang menggunakan konfigurasi `revalidate` tingkat segmen. Hal ini akan memicu konflik logika di mana Next.js kebingungan apakah harus mengambil data secara fresh setiap saat atau melayani cache statis, yang berujung pada lonjakan latensi yang tidak terduga.

Ketika aplikasi sudah mengudara dan traffic harian melonjak dari ratusan menjadi ratusan ribu request per menit, di sinilah ketahanan sistem diuji. Kita tidak bisa lagi hanya mengandalkan satu instance Node.js. Kita harus menjalankan cluster instance di belakang load balancer, atau mendistribusikan beban ke edge network menggunakan platform seperti Vercel atau custom server dengan Redis sebagai shared cache layer jika melakukan self-hosting.

Untuk menguji sejauh mana ketahanan arsitektur yang kita bangun, pengujian performa wajib dilakukan menggunakan tools seperti `k6` atau `Apache Bench`. Jangan menguji hanya dengan membuka browser pribadi. Lakukan simulasi beban nyata.

Berikut adalah skrip pengujian `k6` sederhana untuk menguji endpoint produk kita di bawah tekanan 500 virtual users:

```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 100 },
    { duration: '1m', target: 500 },
    { duration: '30s', target: 0 },
  ],
};

export default function () {
  const res = http.get('https://example.com/products/sepatu-lari-pro-X');
  check(res, {
    'status adalah 200': (r) => r.status === 200,
    'waktu muat di bawah 200ms': (r) => r.timings.duration < 200,
  });
  sleep(1);
}
```

Sebelum tombol 'deploy to production' ditekan, mari kita jalankan checklist terakhir layaknya seorang pilot penerbangan komersial:
1. Pastikan variabel environment terkini sudah terpasang di sistem manager (seperti Dotenv atau Secrets Manager).
2. Verifikasi ulang bahwa tidak ada koneksi database yang dibiarkan terbuka (leak) pada fungsi `generateStaticParams`.
3. Periksa aturan Cache-Control pada CDN Cloudflare agar asset statis (`/_next/static/*`) di-cache secara agresif, sementara HTML halaman ISR menghormati header `s-maxage`.

Dengan merangkai seluruh lapisan ini—mulai dari Nginx, optimasi query database, penerapan ISR yang disiplin, hingga manajemen edge caching—kita tidak hanya menyelamatkan infrastruktur dari ancaman runtuh saat jam sibuk, tetapi juga menghadirkan pengalaman berselancar yang mulus dan instan bagi para pengguna di ujung sana.