โ† semua materi
15:00 Teknis ยท 06 Nov 2026 ยท 15:06

๐Ÿ“š Next.js App Router: Lapisan Caching & Sumber Bug "Data Basi"

Versi referensi: Next.js 16 (rilis Oktober 2025, blog resmi nextjs.org/blog/next-16). Perubahan besar vs 14/15 โ€” materi ini menyebut dua model: legacy model (tanpa cacheComponents) dan Cache Components (model baru).


๐ŸŽฏ Tujuan Belajar

Besok bisa: - Menjelaskan 4 lapisan cache Next.js App Router dan mana yang menyebabkan "data basi" di kasus tertentu - Debug laporan "sudah revalidate tapi UI masih lama" dengan checklist per lapisan - Memakai model baru Next.js 16: use cache, cacheLife, cacheTag, updateTag, refresh - Memilih API invalidasi yang benar untuk Server Action vs webhook


๐Ÿ—๏ธ Empat Lapisan Caching (diagnosis bug "data basi")

Lapisan Lokasi Di-invalidate oleh Gejala klasik kalau lupa
1. Request Memoization (React cache) Dalam 1 render pass Otomatis per request โ€” (bukan sumber basi antar-request)
2. Data Cache / use cache Server, persisten revalidateTag, revalidatePath, TTL (cacheLife/revalidate) Data server basi meski deploy ulang tak selalu (fetch cache bisa lintas deploy)
3. Full Route Cache / prerender Server (HTML/RSC payload) revalidatePath, rebuild Halaman statis masih versi build lama
4. Client Router Cache Browser, in-memory Server Action (revalidateTag/updateTag/refresh), navigasi hard, TTL staleTimes Paling sering jadi biang keladi: server sudah fresh, tapi klik <Link> masih render payload lama

Aturan praktis: keluhan "lihat di halaman A sudah update, halaman B masih lama" atau "refresh dua kali baru berubah" โ†’ lapisan 4 (Client Router Cache) atau kombinasi. Keluhan "bahya request langsung ke server masih lama" โ†’ lapisan 2/3.

Versi 15+ legacy: fetch tidak di-cache default (opt-in via cache: 'force-cache' / next.revalidate / next.tags). Client Router Cache punya staleTimes default: static 5 menit, dynamic 0 detik (eksperimental). Cache entry di Client Router Cache yang di-prefetch bisa basi sampai 5 menit walau server sudah revalidate โ€” kasus GitHub discussion #86538 (Nov 2025) persis ini, fix: turunkan experimental.staleTimes.static.


๐Ÿ’ป Kode Nyata โ€” Model Baru Next.js 16

Enable:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true, // 'use cache' dkk. aktif; PPR jadi bagian dari ini
}
export default nextConfig

Cached data function + tag + lifetime:

// lib/posts.ts โ€” Next.js 16, cacheComponents: true
import { cacheLife, cacheTag } from 'next/cache'

export async function getPost(slug: string) {
  'use cache'
  cacheLife('days')          // stale 5m, revalidate 1d, expire 1w
  cacheTag(`post-${slug}`, 'posts')

  return db.post.findUnique({ where: { slug } })
}

Preset cacheLife (docs resmi, di-update Sep 2026):

Profile stale (client) revalidate (server) expire
default 5 menit 15 menit never
seconds 30 s 1 s 1 menit
minutes 5 m 1 m 1 jam
hours 5 m 1 jam 1 hari
days 5 m 1 hari 1 minggu
max 5 m 30 hari 1 tahun

Tiga angka ini beda maknanya: - stale โ†’ boleh serve tanpa cek server (client router pakai x-nextjs-stale-time header; minimum 30 detik di-enforce) - revalidate โ†’ lewat waktu itu, serve cache + refresh di background (SWR, seperti ISR) - expire โ†’ lewat waktu itu, request blocking sampai fresh

Invalidasi โ€” pilih sesuai konteks:

// 1) Server Action, user HARUS langsung lihat hasilnya (read-your-writes)
'use server'
import { updateTag } from 'next/cache'
export async function createPost(formData: FormData) {
  const post = await db.post.create({ data: { title: formData.get('title') } })
  updateTag('posts')             // hanya boleh dari Server Action; request
  updateTag(`post-${post.id}`)   // berikutnya BLOCKING sampai fresh
}

// 2) Route Handler / webhook CMS โ€” stale-while-revalidate
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
export async function POST(req: Request) {
  if (req.headers.get('x-secret') !== process.env.REVAL_SECRET) {
    return Response.json({ ok: false }, { status: 401 })
  }
  const { id } = await req.json()
  revalidateTag(id, 'max')       // Next 16: argumen ke-2 WAJIB
  return Response.json({ revalidated: true })
}

// 3) refresh() โ€” Server Action, hanya refresh data TIDAK di-cache
import { refresh } from 'next/cache'
refresh() // notification count, live metrics โ€” cache shell tak tersentuh

Catatan penting Next 16: - revalidateTag(tag) satu argumen โ†’ deprecated. Tanpa argumen kedua behave seperti { expire: 0 }. Pakai revalidateTag(tag, 'max') untuk SWR. - updateTag hanya valid di Server Action โ€” dipanggil di Route Handler โ†’ throw. Di webhook pakai revalidateTag(tag, { expire: 0 }) kalau butuh expire instan. - Dengan cacheComponents: true, export dynamic, revalidate, fetchCache di route segment โ†’ build gagal. Semua caching pindah ke use cache. - Cache key use cache = build ID + hash fungsi + argumen seriable. Cache tidak lintas deploy (berbeda dengan fetch Data Cache & unstable_cache yang persisten). - use cache default = in-memory di server. Di serverless (instance ephemeral), entry praktis tak reuse antar-request โ†’ pertimbangkan 'use cache: remote' (cache handler Redis/KV, ada biaya network/platform). - Runtime API (cookies(), headers(), searchParams) dilarang di dalam use cache โ€” baca di luar, pass sebagai argumen. Pelanggaran langsung: error next-request-in-use-cache; variasi tak langsung (Promise runtime lewat props/closure/Map bersama) โ†’ build hang 50 detik dengan error "Filling a cache during prerender timed out". - Versi 16.4+: ensureStatic = 'navigation' untuk memvalidasi route benar-benar statis (pengganti aman dynamic = 'force-static').

Legacy (tanpa cacheComponents) masih didukung โ€” docs-nya kini dipisah jadi "Caching and Revalidating (Previous Model)". Di model itu: fetch(url, { next: { tags, revalidate } }), export const revalidate = 60, revalidatePath.


๐Ÿ“Š Angka & Benchmark


โš–๏ธ Trade-off

Pilihan Untung Rugi
Legacy model (fetch + revalidate) Stabil, banyak tutorial, cache lintas deploy (unstable_cache/fetch) Implicit, dua model API nyampur, sumber kebingungan
cacheComponents: true Eksplisit (opt-in), PPR gratis, build memaksa migrasi segmen lama Breaking: segmen config dilarang, edge runtime tak didukung, in-memory cache hilang saat deploy
revalidateTag(tag, 'max') Cepat (serve stale), cocok konten toleran delay User bisa lihat data lama satu request
updateTag(tag) Read-your-writes, zero stale Hanya Server Action; request berikutnya blocking
use cache in-memory Nol infrastruktur Serverless: tiap request bisa cache miss; hilang saat deploy
use cache: remote Persisten lintas instance Network roundtrip, biaya platform, tetap reset pas deploy

๐Ÿญ Use Case Produksi & Failure Mode

  1. Webhook CMS publish โ†’ data tetap lama di UI selama menit. Penyebab: webhook cuma revalidateTag (lapisan 2/3), Client Router Cache di browser user belum expire. Fix: invalidasi dari Server Action pakai updateTag, atau terima SWR delay.
  2. revalidateTag di middleware/proxy โ†’ tidak jalan (server context salah). Pindah ke Route Handler atau Server Action.
  3. Build hang 50 s di use cache โ†’ Promise runtime (cookies/searchParams) bocor masuk scope cached via props/closure/Map. Refactor: baca di luar, pass nilai primitif.
  4. Serverless + use cache โ†’ hit rate nyaris nol, DB kena trafik penuh. Fix: 'use cache: remote' atau custom cache handler.
  5. User A update, user A sendiri masih lihat lama โ†’ pakai updateTag, bukan revalidateTag(tag, 'max') (SWR sengaja serve stale).
  6. Data beda antar halaman setelah revalidate (kasus nyata discussion #86538, Next 15.5) โ†’ kombinasi fetch cache fresh + Client Router Cache stale. Turunkan staleTimes atau trigger revalidasi via Server Action.

๐Ÿ› ๏ธ Latihan Praktis

npx create-next-app@latest cache-lab --ts --app
cd cache-lab
# enable cacheComponents di next.config.ts, lalu:
NEXT_PRIVATE_DEBUG_CACHE=1 npm run dev
  1. Buat komponen dengan 'use cache' + cacheLife('minutes') + cacheTag('demo'), fetch dari API publik (misal jsonplaceholder).
  2. Bikin Route Handler POST /api/revalidate yang panggil revalidateTag('demo', 'max').
  3. Buka dengan NEXT_PRIVATE_DEBUG_CACHE=1 โ€” amati log cache hit/miss saat navigasi ulang.
  4. Ubah API data, panggil webhook, lihat: request pertama masih stale, request kedua fresh โ€” bukti SWR.
  5. Coba baca cookies() di dalam scope use cache โ†’ amati error next-request-in-use-cache, lalu refactor jadi pass-argumen.

โœ… Cek Pemahaman

  1. Server log menunjukkan revalidateTag sudah jalan dan data server fresh, tapi user yang navigasi via <Link> masih melihat konten lama. Lapisan cache mana yang jadi penyebab, dan dua cara memperbaikinya?
  2. Beda perilaku revalidateTag(tag, 'max') vs updateTag(tag) pada request yang datang tepat setelah pemanggilan?
  3. Kenapa use cache dengan stale < 30 detik di-exclude dari prerender/App Shell?

(Jawaban: 1. Client Router Cache; fix via Server Action updateTag/refresh, atau turunkan staleTimes. 2. 'max' = serve stale + revalidate background; updateTag = blocking sampai fresh. 3. Prefetch link akan expire sebelum user sempat klik โ€” cache entry tak berguna.)


๐Ÿ”‘ Poin Kunci


โžก๏ธ Eksplorasi Lanjutan

Sumber: nextjs.org/blog/next-16 (Okt 2025) ยท docs use cache, cacheLife, cacheTag, revalidateTag, updateTag, staleTimes, "Migrating to Cache Components", "Caching without Cache Components" (update terakhir Agtโ€“Sep 2026) ยท GitHub discussion vercel/next.js #86538 & #91785.