Next.js 16'da use cache: Cache Components ile sunucu tarafı önbellekleme
use cache artık canary değil — Next.js 16 ile Cache Components'in parçası olarak kararlı sürümde. Nasıl açılır, önbellek anahtarı nasıl üretilir, cookies() neden içeride çalışmaz ve hangi durumda hâlâ unstable_cache gerekir.
İlk yazdığımda use cache Next.js 15'in canary sürümündeydi ve "production'da
kullanma" diyordum. Artık öyle değil: Next.js 16.0 ile Cache Components
özelliğinin parçası olarak kararlı sürüme geçti. Yazıyı 16.x dokümantasyonuna
göre baştan yazdım — hem doğru kullanım hem de en sık takılınan yerler burada.
Next.js'te sunucu tarafında önbellekleme uzun süre dağınık bir konuydu: fetch
çağrıları için ayrı davranış, unstable_cache için ayrı bir API, aynı istek
içindeki tekrarlar için React.cache. use cache bu üçünün yerine tek bir
direktif koyuyor — bir fonksiyonun, bileşenin ya da tüm bir dosyanın çıktısını
önbelleğe alıyorsun ve anahtarı Next.js kendisi üretiyor.
Önce: açmadan çalışmıyor
En sık atlanan adım bu. use cache yazmak tek başına yetmiyor; Cache
Components özelliğini açman gerekiyor:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;Bunu açmadan direktifi yazarsan hiçbir şey önbelleğe alınmaz ve sessizce çalışmaya devam eder — hata da almazsın. Yavaşlığın sebebini ararken en çok zaman kaybettiren şey bu.
Nereye yazılır?
Üç yer var ve üçünde de fonksiyon async olmak zorunda:
// 1. Fonksiyon seviyesi
export async function getData() {
"use cache";
const res = await fetch("https://api.example.com/data");
return res.json();
}
// 2. Bileşen seviyesi — prop'lar önbellek anahtarına girer
export async function Bookings({ type = "haircut" }: { type: string }) {
"use cache";
const res = await fetch(`https://api.example.com/bookings?type=${type}`);
return <List items={await res.json()} />;
}// 3. Dosya seviyesi — dosyadaki her export önbelleğe alınır
"use cache";
export async function getMonthlyTotals(accountId: string) {
return db.orders.aggregate({ where: { accountId }, _sum: { amount: true } });
}
export async function getTopProducts() {
return db.products.findMany({ orderBy: { sales: "desc" }, take: 10 });
}Dosya seviyesinde kullanırsan generateMetadata ve generateStaticParams
dahil her export kapsama girer; hepsinin async olması gerekir. Bir route
segmentini tamamen önceden üretmek istiyorsan direktifi o rotayı oluşturan her
dosyanın (page, layout, paralel rota slotları) başına ayrı ayrı koymalısın —
her segment bağımsız bir önbellek girdisi.
Önbellek anahtarı nasıl üretiliyor?
Anahtar dört parçadan oluşuyor:
- Build ID — her derlemede değişir, yani her dağıtım tüm önbelleği geçersiz kılar
- Fonksiyon kimliği — fonksiyonun kod içindeki konumu ve imzasının güvenli özeti
- Serileştirilebilir argümanlar — bileşende prop'lar, fonksiyonda parametreler
- HMR özeti — yalnız geliştirmede, sıcak yenilemede önbelleği düşürür
Buradaki asıl incelik dördüncü maddede değil, argümanlarda: dış kapsamdan kullandığın değişkenler de otomatik olarak argüman sayılıp anahtara giriyor.
async function Component({ userId }: { userId: string }) {
const getData = async (filter: string) => {
"use cache";
// Anahtar hem userId'yi (kapanıştan) hem filter'ı (argümandan) içerir
const res = await fetch(
`https://api.example.com/users/${userId}/data?filter=${filter}`
);
return res.json();
};
return getData("active");
}Bu iyi haber: yanlışlıkla bir kullanıcının verisini başkasına göstermiyorsun. Kötü haber: farkında olmadan anahtarı çeşitlendirip önbellek isabet oranını düşürebilirsin. İçeride kullandığın her dış değişkenin anahtarı böldüğünü unutma.
Ne kadar taze kalacak: cacheLife
cacheLife çağırmazsan default profili uygulanır:
- stale: 5 dakika (istemci tarafı)
- revalidate: 15 dakika (sunucu tarafı)
- expire: zamanla hiç dolmaz
import { cacheLife } from "next/cache";
export async function getOrderSummary(accountId: string) {
"use cache";
cacheLife("hours");
const orders = await getOrders(accountId);
return { orders };
}Dokümantasyonun önerisi net: her use cache kapsamında cacheLife'ı açıkça
yaz. Yoksa davranış çağrı yerinde görünmüyor, varsayılana ya da onu saran
önbelleğe bağlı kalıyor.
Bir uyarı: cacheLife verilmemiş bir use cache içine kısa ömürlü bir başka
use cache yerleştirirsen derleme hata verir. İç içe önbelleklerde ömürleri
bilerek seçmen gerekiyor.
Etiketle ve elle temizle: cacheTag
Zamana bağlı tazelenme her zaman doğru cevap değil. İçerik değiştiğinde anında temizlemek istiyorsan etiket kullan:
// lib/products.ts
import { cacheTag } from "next/cache";
export async function getProducts() {
"use cache";
cacheTag("products");
const res = await fetch("https://api.example.com/products");
return res.json();
}// app/actions.ts
"use server";
import { updateTag } from "next/cache";
export async function updateProduct() {
await db.products.update(/* ... */);
updateTag("products"); // "products" etiketli tüm girdiler düşer
}Pratikte ikisi birlikte kullanılıyor: nadiren değişen bir blog yazısı uzun bir
cacheLife + cacheTag alıyor ve yalnız kaydedildiğinde temizleniyor; gün
içinde akan bir liste ise kısa bir profille kendi kendine tazeleniyor.
En sık takılınan yer: cookies() içeride çalışmaz
Önbelleğe alınmış bir kapsamda cookies(), headers() ve searchParams
kullanılamıyor. Üstelik kısıt çağrı yığınını takip ediyor: içeriden
çağırdığın bir yardımcı fonksiyon bunlardan birini okuyorsa o da patlıyor
(next-request-in-use-cache).
Dinamik olarak render edilen bir rotada bu hata derlemede değil, çalışma
anında ortaya çıkıyor. Yani next build sorunsuz geçip next start altında
hata alabilirsin. CI'da yeşil, canlıda kırmızı.
Doğru kalıp: istek verisini önbellek kapsamının dışında oku, içeriye argüman olarak geçir.
// YANLIŞ — çalışma anında patlar
async function getCart() {
"use cache";
const store = await cookies();
return db.cart.find({ id: store.get("cart")?.value });
}
// DOĞRU — okuma dışarıda, argüman içeride
export default async function Page() {
const store = await cookies();
const cartId = store.get("cart")?.value;
return <Cart cartId={cartId} />;
}
async function Cart({ cartId }: { cartId?: string }) {
"use cache";
return <CartView data={await db.cart.find({ id: cartId })} />;
}Gerçekten kaçınılmaz olduğu durumlar için use cache: private var, ama
dokümantasyon bunu "çok nadiren" diye işaretliyor — uyum zorunluluğu ya da kodu
yeniden düzenleyemediğin haller için.
Serileştirme sınırları
Argümanlar ve dönüş değerleri serileştirilebilir olmalı.
Geçerli: ilkel tipler, düz nesneler, diziler, Date, Map, Set,
TypedArray, ArrayBuffer. Dönüş değeri ayrıca JSX olabilir.
Geçersiz: sınıf örnekleri, fonksiyonlar, Symbol, WeakMap/WeakSet ve —
kolay unutulan — URL örnekleri.
Buradaki kurtarıcı ayrıntı: bir değeri içine bakmadan geçirip
kullanabiliyorsun. children ve Server Action'lar böyle çalışıyor.
async function CachedWrapper({ children }: { children: ReactNode }) {
"use cache";
// children okunmuyor, sadece geçiriliyor — anahtarı etkilemiyor
return (
<div className="wrapper">
<header>Önbelleğe alınmış başlık</header>
{children}
</div>
);
}
export default function Page() {
return (
<CachedWrapper>
<DynamicComponent /> {/* önbelleğe alınmıyor */}
</CachedWrapper>
);
}Bu sayede sayfanın statik kabuğunu önbelleğe alıp içine dinamik parçayı
yerleştirebiliyorsun. Bir layout'a use cache koyman, altındaki children'ı
önbelleğe almaz.
YouTube
Konuyu video anlatımıyla da takip edebilirsin.
Kanala git →
Nerede saklanıyor? Barındırma biçimi sonucu değiştiriyor
Varsayılan işleyici bellek içi LRU. Bu, nerede yayınladığına göre çok farklı davranıyor:
| Ortam | Çalışma anındaki davranış |
|---|---|
| Serverless | Girdiler istekler arasında genelde kalmaz; her istek farklı bir örneğe düşebilir. Derleme anındaki önbellekleme normal çalışır. |
| Kendi sunucun | Girdiler istekler arasında kalır. Boyutu cacheMaxMemorySize ile ayarlarsın. |
"use cache koydum ama veritabanı yükü düşmedi" şikâyetinin çoğu bu tablodan
çıkıyor. Serverless'ta bellek içi önbellek kalıcı değil. Gerçekten paylaşımlı
bir önbellek istiyorsan use cache: remote ile ayrı bir işleyici (Redis, KV)
bağlaman gerekiyor — bunun da ağ gecikmesi ve platform maliyeti var.
Ayrıca hiçbir önbellek direktifi yeni bir dağıtıma taşınmaz, çünkü anahtar
build ID'yi içeriyor. Dağıtımlar arasında kalması gereken veri için hâlâ
unstable_cache ya da fetch önbelleği kullanman gerekiyor. use cache her
şeyin yerini almıyor.
Derleme 50 saniyede takılıyorsa
Şu hatayı görüyorsan sebebi neredeyse her zaman aynı:
Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or uncached data were used inside "use cache".
Önbelleğe alınmış kapsam, derleme sırasında çözülemeyecek bir Promise'i
bekliyor. Tipik üç yol: böyle bir Promise'i prop olarak geçirmek, kapanıştan
erişmek, ya da paylaşılan bir Map üzerinden almak. Çözüm aynı: değeri dışarıda
await et, içeriye düz veri geçir.
cookies()'i doğrudan içeride çağırmak ise zaman aşımı değil, anında hata verir
— ikisini karıştırma.
Bilmen gereken diğer iki davranış
Taslak modu (Draft Mode) açıkken tüm önbelleğe alınmış fonksiyonlar her istekte yeniden çalışır ve sonuç kaydedilmez. Önizlemede bayat içerik görme riski yok; bunun için ekstra kod yazmana gerek kalmıyor.
React.cache yalıtılmıştır. use cache sınırının dışında React.cache ile
sakladığın bir değer içeriden görünmez. Veriyi içeri sokmanın tek yolu argüman.
Ne zaman kullanmamalı?
- Statik dışa aktarımda (
output: "export") desteklenmiyor. Node.js sunucusu ve Docker destekli. - Kullanıcıya özel, her istekte değişen veride — kapsam dışına çıkarıp argüman geçirmen gerekir, o da çoğu zaman önbelleği anlamsız kılar.
- Dağıtımlar arasında yaşaması gereken pahalı hesaplamalarda; build ID anahtarı sıfırlıyor.
- Verinin tazeliği saniyelik önem taşıyorsa: istemci yönlendiricisi en az 30 saniyelik bir bayatlık süresi dayatıyor, yapılandırmadan bağımsız olarak.
Hata ayıklama
NEXT_PRIVATE_DEBUG_CACHE=1 npm run dev
# ya da üretim için
NEXT_PRIVATE_DEBUG_CACHE=1 npm run startGeliştirmede önbelleğe alınmış fonksiyonlardan gelen console.log çıktıları
Cache önekiyle görünüyor — hangi çağrının önbellekten döndüğünü anlamanın en
hızlı yolu.
Özet
use cacheNext.js 16.0'da kararlı;cacheComponents: trueile açılıyor- Fonksiyon, bileşen, dosya ve route segmenti seviyesinde çalışıyor; hepsi async olmalı
- Anahtar: build ID + fonksiyon kimliği + argümanlar + kapanıştan yakalanan değişkenler
cacheLifeile ömür,cacheTag+updateTagile elle temizlemecookies(),headers(),searchParamsiçeride yasak — dışarıda oku, argüman geçir- Varsayılan depolama bellek içi LRU; serverless'ta istekler arasında kalmaz
- Dağıtımlar arası kalıcılık gerekiyorsa hâlâ
unstable_cacheya dafetchönbelleği
Bu yazının ilk hâlinde bir "performans testi" bölümü vardı; %78 hızlanma gibi
sayılar veriyordu. Onu kaldırdım, çünkü o sayılar gerçek bir ölçümden değil
uydurma bir setTimeout kurgusundan geliyordu ve senin projendeki kazancı
tahmin etmene yaramıyordu. Kazanç tamamen neyi önbelleğe aldığına bağlı: 200 ms
süren bir veritabanı sorgusu ile 2 ms süren bir hesaplamayı önbelleğe almak aynı
şey değil. Kendi ölçümünü NEXT_PRIVATE_DEBUG_CACHE=1 ile yap.
Bu yazı Next.js 16.x resmî dokümantasyonuna (use cache
direktifi)
dayanıyor. Sürüm geçmişi: v15.0.0 deneysel olarak tanıttı, v16.0.0 Cache
Components ile kararlı hâle getirdi.
Bu yazıyı faydalı bulduysan paylaşabilirsin.