Waktu respons SSR biasanya berkisar 200-800ms tanpa caching. Cache hit ISR kembali dalam ~80ms. Pada 100.000 permintaan per hari, SSR biayanya 10-50x lebih mahal dalam komputasi daripada ISR yang menyajikan halaman yang di-cache. Saya belajar hal ini dengan cara yang sulit ketika saya meluncurkan dashboard Next.js dengan SSR untuk setiap halaman dan melihat biaya server melonjak. Strategi caching bukan hanya masalah performa — ini adalah keputusan biaya infrastruktur.
SSG menghasilkan HTML pada waktu build. ISR menghasilkan HTML pada permintaan pertama dan menyimpannya, memvalidasi ulang di latar belakang setelah interval waktu. SSR menghasilkan HTML pada setiap permintaan. RSC adalah model rendering yang bekerja di semua tiga — memindahkan rendering komponen ke server, mengurangi JS klien. Memahami mode mana yang digunakan memerlukan pemahaman dua hal: seberapa sering data berubah, dan apakah personalisasi memerlukan data waktu permintaan.
ISR adalah default yang tepat untuk sebagian besar halaman. Ini memberi Anda performa situs statis (cache hit ~80ms TTFB, disajikan dari CDN) dengan kemampuan memperbarui konten tanpa rebuild penuh. Di Next.js App Router, Anda mengaktifkan ISR dengan mengatur `revalidate` di panggilan fetch atau konfigurasi segmen rute. Interval revalidasi 60 detik sesuai untuk daftar blog yang diperbarui beberapa kali sehari.
Revalidasi berbasis waktu (revalidate: 60) sederhana tetapi tidak tepat — konten bisa basi hingga 60 detik setelah perubahan. Revalidasi on-demand dengan `revalidatePath()` atau `revalidateTag()` memungkinkan Anda membatalkan entri cache tertentu segera saat konten berubah. Untuk blog, panggil `revalidatePath('/blog')` dari webhook saat postingan baru diterbitkan.
Next.js App Router — 4 Cache Layers
────────────────────────────────────────────────────────────
Layer 1: Request Memoization
Same fetch() URL called multiple times in one render?
→ Deduped automatically, hits network once
Layer 2: Data Cache (persistent, cross-request)
fetch(url, { next: { revalidate: 60 } }) ← time-based ISR
fetch(url, { next: { tags: ['posts'] } }) ← tag-based invalidation
revalidatePath('/blog') ← on-demand invalidation
revalidateTag('posts') ← tag invalidation
Layer 3: Full Route Cache (CDN edge, HTML + RSC payload)
export const revalidate = 3600 ← route-level ISR
export const dynamic = 'force-dynamic' ← disable route cache → SSR
Layer 4: Router Cache (browser, client-side navigation)
Next.js prefetches and caches route segments
Expires: 30s (dynamic), 5min (static) — not configurable
Performance comparison:
SSG cache hit: ~10ms TTFB (CDN edge)
ISR cache hit: ~80ms TTFB (CDN edge, stale-while-revalidate)
ISR cache miss: ~300ms TTFB (origin render + cache store)
SSR (no cache): ~200-800ms TTFB (origin render every time)Dari pengalaman membangun aplikasi Next.js yang berat data: gunakan caching fetch di tingkat komponen, bukan tingkat halaman. Daripada mengatur `export const revalidate = 60` pada rute, atur opsi cache per-fetch: `fetch(url, { next: { revalidate: 60 } })`. Ini memberi Anda kontrol granular — harga produk mungkin revalidate setiap 5 menit, sementara deskripsi produk dapat di-cache selama sehari.
SSR sesuai ketika respons harus dipersonalisasi untuk pengguna spesifik yang membuat permintaan, dan personalisasi tersebut tidak dapat dilakukan sisi klien setelah shell statis dimuat. Contoh nyata: dashboard yang menampilkan data yang difilter berdasarkan organisasi pengguna yang masuk, halaman checkout yang harus membaca keranjang pengguna dari sesi, atau halaman yang kontennya dibatasi di balik pemeriksaan peran yang harus terjadi sisi server demi keamanan.
Next.js App Router memiliki empat lapisan caching: Request Memoization (deduplication dalam satu render), Data Cache (persisten, lintas permintaan), Full Route Cache (HTML dan payload RSC, disimpan di edge CDN), dan Router Cache (sisi klien, di browser, untuk navigasi). Memahami semua empat lapisan penting karena mereka berinteraksi.
// app/blog/page.tsx — ISR blog listing
export const revalidate = 3600 // revalidate at most every hour
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600, tags: ['posts'] },
}).then(r => r.json())
return <PostList posts={posts} />
}
// app/api/revalidate/route.ts — webhook endpoint
import { revalidateTag } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(req: NextRequest) {
const secret = req.headers.get('x-webhook-secret')
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
revalidateTag('posts')
return NextResponse.json({ revalidated: true, timestamp: Date.now() })
}
// For database queries (not fetch), use React cache() or unstable_cache:
import { cache } from 'react'
import { unstable_cache } from 'next/cache'
// React cache: memoizes within a single render only
const getCachedUser = cache(async (id: string) => {
return prisma.user.findUnique({ where: { id } })
})
// unstable_cache: persists across requests, supports tags
const getCachedPosts = unstable_cache(
async () => prisma.post.findMany({ orderBy: { createdAt: 'desc' } }),
['all-posts'], // cache key
{ tags: ['posts'], revalidate: 3600 }
)Di Next.js App Router, komponen server dapat mengkueri database secara langsung. Kueri-kueri ini mendapat manfaat dari Data Cache jika dibungkus dalam `cache()` dari React. Untuk fungsi async non-fetch (seperti kueri Prisma), gunakan fungsi `cache()` React untuk memoize dalam satu render. Untuk caching lintas permintaan hasil database, `unstable_cache` dari Next.js menyediakan cache persisten dengan invalidasi berbasis tag.
Saya telah melihat pengembang default ke SSR untuk setiap halaman karena 'data perlu segar.' Dalam kebanyakan kasus, kesegaran data mentoleransi jendela revalidasi 30-60 detik, membuat ISR menjadi pilihan yang tepat. SSR berarti setiap pemuatan halaman memunculkan render sisi server — pada 100 pengguna bersamaan, itu 100 proses server simultan. Profil persyaratan kesegaran aktual Anda sebelum memilih SSR.
Halaman ISR dan SSG secara otomatis di-cache di edge CDN oleh Vercel. Untuk deployment self-hosted, Anda perlu mengkonfigurasi reverse proxy (nginx atau Caddy) untuk menyimpan respons. Server Next.js mengatur header Cache-Control berdasarkan konfigurasi rute Anda. CDN Anda harus menghormati header ini.
Saya menggunakan pohon keputusan sederhana untuk setiap rute: Apakah memerlukan data spesifik pengguna yang harus sisi server? → SSR. Apakah data berubah lebih dari sekali per menit? → SSR atau ISR dengan interval pendek + revalidasi on-demand. Apakah berubah beberapa kali sehari? → ISR dengan revalidate 60-300d. Apakah sepenuhnya statis? → SSG dengan revalidasi on-demand untuk perubahan.