Next.js data fetching, App Router'a geçtikten sonra çoğu geliştiricinin kafasını karıştıran konuların başında geliyor. Çünkü artık veriyi nereden ve nasıl çektiğinizden çok, o verinin ne zaman ve ne kadar süre önbelleğe alındığı sayfanızın davranışını belirliyor. Aynı fetch çağrısı, eklediğiniz birkaç seçenekle sayfayı tamamen statik, her istekte yeniden üretilen ya da belirli aralıklarla tazelenen bir hâle getirebilir. Bu yazıda static rendering, SSR ve ISR arasındaki farkları somut örneklerle açıklıyorum.
Server Component'lerde veri çekmenin temeli
App Router'da bileşenler varsayılan olarak Server Component'tir. Bu da şu anlama gelir: bileşeni async yapıp doğrudan içinde await fetch(...) çağırabilirsiniz. Ayrı bir useEffect, durum yönetimi ya da yükleme bayrağına gerek kalmaz; veri sunucuda hazırlanır ve HTML'e gömülerek tarayıcıya gider.
// app/urunler/page.tsx
export default async function UrunlerPage() {
const res = await fetch('https://api.ornek.com/urunler');
const urunler = await res.json();
return (
<ul>
{urunler.map((u) => (
<li key={u.id}>{u.ad}</li>
))}
</ul>
);
}
Buradaki kritik nokta şu: bu kodun statik mi yoksa dinamik mi çalışacağı, fetch'e verdiğiniz cache ve next seçeneklerine bağlıdır.
Static rendering: derleme anında üret, sonsuza kadar sakla
Static rendering, sayfayı build (derleme) sırasında bir kez üretir ve sonuçtaki HTML'i kullanıcıya tekrar tekrar sunar. Blog yazıları, dokümantasyon, ürün açıklamaları gibi sık değişmeyen içerikler için idealdir; çünkü her istek bir CDN dosyası kadar hızlı yanıtlanır.
Önemli bir ayrıntı: Next.js 15 ile birlikte fetch isteklerinin varsayılan davranışı değişti. Artık varsayılan olarak önbelleğe alınmaz (Next.js 14'te varsayılan önbellekliydi). Statik ve önbellekli davranış istiyorsanız bunu açıkça belirtmeniz gerekir:
// Sonucu kalıcı olarak önbelleğe al -> statik
const res = await fetch('https://api.ornek.com/urunler', {
cache: 'force-cache',
});
Dinamik segmentli rotalarda (örneğin app/blog/[slug]/page.tsx) hangi sayfaların önceden üretileceğini generateStaticParams ile bildirirsiniz:
export async function generateStaticParams() {
const yazilar = await fetch('https://api.ornek.com/yazilar').then((r) => r.json());
return yazilar.map((y) => ({ slug: y.slug }));
}
SSR: her istekte taze veri
Server-Side Rendering (SSR), sayfayı her gelen istekte yeniden sunucuda üretir. Kullanıcıya özel paneller, sepet, anlık fiyatlar veya sürekli değişen veriler için doğru seçimdir. Bunu sağlamak için önbelleği tamamen kapatırsınız:
const res = await fetch('https://api.ornek.com/fiyatlar', {
cache: 'no-store',
});
Aynı etkiyi rota segmenti seviyesinde de elde edebilirsiniz. Sayfanın en üstüne aşağıdaki satırı eklediğinizde o rotadaki tüm veri her istekte yeniden çekilir:
export const dynamic = 'force-dynamic';
Ayrıca cookies(), headers() ya da arama parametrelerini (searchParams) okuduğunuzda rota otomatik olarak dinamiğe geçer; çünkü çıktı her kullanıcıya göre farklılaşabilir.
ISR: statik hız + periyodik tazelik
Incremental Static Regeneration (ISR), static rendering'in hızıyla SSR'ın güncelliği arasında bir köprü kurar. Sayfa statik olarak sunulur, ancak belirlediğiniz süre dolduğunda arka planda yeniden üretilir. Yani kullanıcılar her zaman hızlı bir önbellekli sürüm alır, içerik de makul bir gecikmeyle güncellenir.
// 60 saniyede bir tazelenecek
const res = await fetch('https://api.ornek.com/urunler', {
next: { revalidate: 60 },
});
Bunu segment seviyesinde de tanımlayabilirsiniz:
export const revalidate = 60;
ISR, e-ticaret listeleri, haber akışları veya saatte birkaç kez değişen panolar için biçilmiş kaftandır: dakikada milyon istek gelse bile yeniden üretim yalnızca süre dolduğunda bir kez tetiklenir.
On-demand revalidation: tam zamanında güncelleme
Sabit bir süre yerine, içerik gerçekten değiştiğinde önbelleği temizlemek isteyebilirsiniz. Next.js bunun için iki yöntem sunar: etikete göre (revalidateTag) ve yola göre (revalidatePath). Önce fetch'i bir etiketle işaretlersiniz:
await fetch('https://api.ornek.com/urunler', {
next: { tags: ['urunler'] },
});
Ardından bir Server Action ya da Route Handler içinde, örneğin yönetim panelinden ürün güncellendiğinde o etiketi geçersiz kılarsınız:
import { revalidateTag } from 'next/cache';
revalidateTag('urunler');
Böylece sayfa hem statik kadar hızlı kalır hem de değişiklik anında yansır. Bu yaklaşım, CMS veya admin paneli olan projelerde ISR'a göre çok daha hassas bir kontrol sağlar.
Doğru stratejiyi seçmek
- Static: nadiren değişen içerik, en yüksek performans.
cache: 'force-cache'. - ISR: sık ama anlık olması gerekmeyen güncellemeler.
next: { revalidate: N }. - SSR: kullanıcıya özel veya her saniye değişen veri.
cache: 'no-store'. - On-demand: olay tetikli güncelleme.
tags+revalidateTag.
Pratik bir tavsiye: çoğu sayfa için ISR ile başlayın, gerçekten gerekmedikçe her şeyi force-dynamic yapmayın. Gereksiz dinamiklik, Next.js'in size sunduğu en büyük avantajı, yani önbelleği, boşa harcar.
Sık Sorulan Sorular
Next.js 14 ile 15 arasında fetch önbelleği neden farklı?
Next.js 15'te fetch varsayılan olarak önbelleğe alınmaz; istediğiniz önbelleği açıkça cache: 'force-cache' veya revalidate ile devreye alırsınız. Next.js 14'te ise tam tersine varsayılan önbellekliydi. Eski projeleri yükseltirken bu farkı kontrol etmek beklenmedik sürprizleri önler.
Client Component içinde nasıl veri çekerim?
Client Component'lerde sunucu önbelleği devreye girmez; klasik yöntemle, örneğin useEffect ile veya SWR / React Query gibi kütüphanelerle veri çekersiniz. Mümkün olduğunca veriyi Server Component'te çekip alt bileşene prop olarak geçmek genelde daha verimlidir.
revalidate ile force-dynamic'i aynı anda kullanabilir miyim?
Hayır, bu ikisi çelişir. force-dynamic sayfayı her istekte üretir ve önbelleği yok sayar, dolayısıyla revalidate süresinin bir anlamı kalmaz. Periyodik tazeleme istiyorsanız revalidate, tam dinamiklik istiyorsanız force-dynamic seçin.
Next.js projenizde veri çekme ve önbellek stratejisi mi kurmak istiyorsunuz? Doğru render modeliyle hem hızlı hem güncel bir site mümkün. Birlikte bakalım: benimle iletişime geçin.