Next.js Cache Components dan Direktif use cache

Foto oleh Egor Litvinov on Unsplash
Direktif use cache menandai sebuah rute, komponen, atau fungsi async sebagai dapat di-cache di Next.js. Direktif ini dibuka oleh flag cacheComponents dan bisa berada di level berkas, komponen, atau fungsi. Next.js membangun kunci cache dari build ID, hash lokasi dan tanda tangan fungsi, serta argumennya yang dapat diserialisasi.
Next.js lama meng-cache data secara implisit lewat default framework, opsi fetch, dan konfigurasi segmen rute. Cache Components membalik hal itu: data bersifat dinamis secara default dan Anda memilih caching secara eksplisit dengan use cache. Keputusan caching kini hidup di dalam kode, bukan tersebar di antara berbagai default.
Profil cacheLife menetapkan tiga waktu pada scope yang di-cache: stale (berapa lama klien menyajikannya tanpa mengecek server), revalidate (seberapa sering server membangunnya ulang di latar belakang), dan expire (batas keras). Next.js menyediakan preset seperti seconds, minutes, hours, days, weeks, dan max, dan Anda bisa membuat profil kustom atau inline.
cacheTag melekatkan satu atau lebih label string pada entri cache, menerima hingga 128 tag masing-masing 256 karakter. Dari Server Action atau Route Handler Anda memanggil revalidateTag dengan sebuah label untuk menghapus setiap entri yang membawanya, menyajikan konten basi sementara ia dibangun ulang. Untuk baca-tulisan-sendiri, updateTag membuat perubahan terlihat pada pembacaan berikutnya.
Mengaktifkan cacheComponents menjadikan Partial Prerendering sebagai default, sehingga flag ppr eksperimental lama dan konfigurasi per-rute dihapus. Next.js memprarender shell HTML statis dari konten yang dapat di-cache, menyajikannya seketika, dan men-stream lubang dinamis begitu selesai. use cache menentukan apa yang masuk ke shell, sementara batas Suspense menandai tempat konten dinamis di-stream.

Foto oleh Egor Litvinov on Unsplash
Ringkasan Utama
Cache Components adalah model Next.js 16 di mana data bersifat dinamis secara default dan Anda memilih untuk melakukan caching lewat direktif use cache. cacheLife menentukan berapa lama sebuah entri tetap segar, cacheTag memberinya label, dan revalidateTag menghapusnya sesuai kebutuhan. Model ini juga menjadikan Partial Prerendering sebagai perilaku render default.
Selama bertahun-tahun saya tidak pernah bisa menjawab satu pertanyaan sederhana dengan yakin: apakah halaman ini di-cache? Di App Router, sebuah fetch bisa di-cache secara default, sebuah segmen rute bisa statis, dan satu API dinamis liar bisa diam-diam membuat semuanya menjadi dinamis. Aturannya hidup di default framework, bukan di kode saya, jadi jawaban jujurnya biasanya angkat bahu lalu mengecek langsung di produksi.
Cache Components, model yang aktif lewat satu flag di Next.js 16, memperbaiki hal itu dengan membalik default-nya. Tidak ada yang di-cache kecuali saya menyatakannya. Saya cukup memakai satu direktif, use cache, untuk menandai persis rute, komponen, atau fungsi yang keluarannya ingin saya simpan, dan sisanya tetap dinamis serta di-stream saat permintaan datang. Tulisan ini membahas seluruh permukaannya: direktifnya, profil cacheLife, invalidasi cacheTag dan revalidateTag, flag konfigurasi, dan bagaimana semuanya menyatu ke Partial Prerendering.
Cerita caching lama bersifat implisit. Permintaan fetch di-cache kecuali Anda memberi opsi no-store, rute penuh diprarender kecuali sesuatu memaksanya dinamis, dan helper seperti unstable_cache menambahkan lapisan ekstra. Hasilnya kuat tetapi sulit dinalar, karena keputusan tentang apa yang di-cache tersebar di antara default, opsi fetch, dan konfigurasi segmen rute, bukan tampak jelas di dalam komponen.
Cache Components membuat keputusan itu eksplisit dan lokal. Pengambilan data bersifat dinamis secara default, dan Anda memilih apa yang di-cache di level halaman, komponen, atau fungsi. Ketika Anda lupa melakukan caching pada sesuatu yang mahal, Anda tetap mendapat keluaran yang benar dan segar, bukan halaman yang diam-diam basi. Konsekuensi yang Anda terima adalah kini Anda menuliskan caching dengan sengaja, dan justru itulah intinya. Berikut perbandingan kedua model tersebut.
| Aspek | Caching implisit (default lama) | use cache eksplisit |
|---|---|---|
| Perilaku default | Data di-cache kecuali Anda menonaktifkannya | Data dinamis kecuali Anda mengaktifkannya |
| Mekanisme opt-in | force-cache, konfigurasi rute, unstable_cache | Satu direktif use cache dalam scope |
| Kendali masa berlaku | Ekspor revalidate dan opsi per-fetch | Profil cacheLife, bernama atau inline |
| Invalidasi | revalidatePath dan revalidateTag | cacheTag plus revalidateTag atau updateTag |
| Di mana keputusan berada | Default framework dan berkas konfigurasi | Terlihat di dalam scope yang di-cache itu sendiri |
Semuanya dimulai dari satu flag. Mengaktifkan cacheComponents di konfigurasi Next.js membuka direktif use cache, fungsi cacheLife, dan cacheTag. Di balik layar, satu flag ini menggantikan tiga opsi eksperimental terpisah yang dulu dikonfigurasi sendiri-sendiri, menyatukan render dinamis, caching, dan prarender menjadi satu pengaturan koheren yang diperkenalkan di Next.js 16.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfigSetelah flag aktif, use cache bisa berada di tiga level. Di bagian atas berkas ia meng-cache setiap ekspor, dan semua ekspor itu harus berupa fungsi async. Di bagian atas komponen ia meng-cache keluaran render komponen tersebut. Di bagian atas fungsi async mana pun ia meng-cache nilai kembalian fungsi itu. Kunci cache dibangun dari build ID, hash lokasi dan tanda tangan fungsi, serta argumen yang dapat diserialisasi, sehingga input berbeda otomatis mendapat entri berbeda.
Direktif itu sendiri memakai default yang masuk akal: konten tetap segar di klien selama lima menit dan direvalidasi di server setiap lima belas menit. Sebagian besar waktu saya ingin lebih sengaja, jadi saya memasangkan use cache dengan cacheLife untuk menentukan masa berlaku dan cacheTag untuk memberi label pada entri. Di sini sebuah daftar produk di-cache dengan profil hours dan diberi tag agar bisa saya hapus nanti.
// app/products/page.tsx
import { cacheLife, cacheTag } from 'next/cache'
async function getProducts() {
'use cache'
cacheLife('hours') // stale 5m, revalidate 1h, expire 1d
cacheTag('products') // label this entry for on-demand purges
const res = await fetch('https://api.example.com/products')
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
return <ProductGrid products={products} />
}Caching hanya separuh pekerjaan; separuh lainnya adalah invalidasi. Ketika stok berubah, sebuah Server Action memanggil revalidateTag dengan label yang sama. Itu menandai entri bertag tersebut basi dan membangunnya ulang di latar belakang, sehingga pembaca tetap mendapat respons instan sementara salinan segar dibuat. Jika saya butuh pembacaan berikutnya langsung melihat perubahan, saya akan memakai updateTag, yang dibuat untuk skenario baca-tulisan-sendiri.
// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function refreshProducts() {
await updateInventory()
// Serve stale, then rebuild the 'products' entry in the background
revalidateTag('products')
}Selalu tetapkan cacheLife eksplisit pada scope yang di-cache. Tanpa itu ia mewarisi revalidate default lima belas menit, dan cache bermasa-hidup pendek yang tersarang bisa diam-diam memperpendek seluruh pohon. Menamai profil membuat siapa pun bisa membaca scope dan tahu kesegarannya sekilas, tanpa menelusuri cache bersarang.
Setiap masa berlaku cache adalah tiga angka. Stale adalah berapa lama router klien menyajikan entri tanpa mengecek server. Revalidate adalah seberapa sering server membangunnya ulang di latar belakang, mirip Incremental Static Regeneration. Expire adalah batas keras: setelah selama itu tanpa lalu lintas, permintaan berikutnya menunggu salinan segar. Next.js menyediakan profil bawaan sehingga Anda jarang menyentuh angka mentahnya.
Anda juga bisa mendefinisikan profil bernama kustom di konfigurasi, atau memberi objek inline untuk kebutuhan sekali pakai. Properti apa pun yang Anda hilangkan mewarisi dari profil default. Satu catatan penting: profil dengan revalidate nol atau expire di bawah lima menit, termasuk profil seconds, diperlakukan sebagai lubang dinamis dan dikecualikan dari shell yang diprarender alih-alih ditanam di dalamnya.
Kedaluwarsa berbasis waktu cocok untuk konten yang terjadwal, tetapi sebagian besar invalidasi digerakkan oleh peristiwa: sebuah produk disunting, sebuah pos diterbitkan, sebuah komentar masuk. cacheTag melekatkan satu atau lebih label string pada entri cache, dan Anda bisa menghasilkan label itu dari datanya sendiri, misalnya memberi tag entri dengan id record. Satu panggilan menerima hingga 128 tag, masing-masing hingga 256 karakter.
Dari sebuah Server Action atau Route Handler Anda lalu memanggil revalidateTag dengan sebuah label untuk menghapus setiap entri yang membawanya. revalidateTag adalah opsi sajikan-basi-sambil-revalidasi: pembaca tetap mendapat salinan lama sampai pembangunan ulang selesai. updateTag adalah saudara yang lebih ketat untuk saat pengguna yang sama yang membuat perubahan harus melihatnya pada pembacaan berikutnya. Keduanya memberi kendali presisi alih-alih menghapus seluruh cache.
Scope yang di-cache berjalan terisolasi dan tidak bisa memanggil cookies, headers, atau membaca searchParams secara langsung; baca semua itu di luar scope dan oper nilainya sebagai argumen. Selain itu, data dinamis yang tak di-cache dan tidak dibungkus dalam batas Suspense akan melempar error saat build, sehingga Anda dipaksa membatasi bagian dinamis dengan sengaja.
Cache Components bukan hanya soal data; ia juga model render. Mengaktifkannya menjadikan Partial Prerendering sebagai default, sehingga flag ppr eksperimental lama dan konfigurasi ppr per-rute sudah dihapus. Next.js memprarender sebuah shell HTML statis dari segala sesuatu yang dapat di-cache, menyajikan shell itu seketika, dan men-stream lubang dinamis begitu ia selesai dihitung, semuanya dalam satu rute.
Itulah sebabnya direktif dan batas Suspense bekerja berpasangan. use cache menentukan apa yang masuk ke shell statis; batas Suspense menandai di mana lubang dinamis di-stream dengan sebuah fallback. Anda berhenti memilih antara halaman sepenuhnya statis atau sepenuhnya dinamis dan malah menggabungkan keduanya di pohon yang sama. Bagi saya kemenangan sesungguhnya adalah keterbacaan: batas cache kini hidup di dalam kode, jadi saya akhirnya bisa menjawab apa yang di-cache dengan membaca berkasnya.