Önemli: Bu yazı güncellendi
İ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:
Örnek: next.config.ts
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:
Örnek: Fonksiyon, bileşen ve dosya seviyesi
// 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.
Önemli: Kapanış (closure) anahtara dahildir
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
İpucu: Ömrü açıkça yaz
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:
Örnek: cacheTag + updateTag
// 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).
Dikkat: Sinsi tarafı
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.
Açıklama: Yanlış / doğru
// 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.
İpucu: Geçiş (pass-through) kalıbı
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. |
Uyarı: Sık yapılan yanlış çıkarım
"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
İpucu: Ayrıntılı günlük
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
Özet: Kısa hâli
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.
Not: Kaynak
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.
İnteraktif Yol Haritası & Araç