2026-08-28arayuz4 dk

Sunucu tarafı render mı, istemci tarafı mı? Karar ağacı

SSR, CSR, SSG ve ISR arasındaki seçim uygulamanın tamamı için değil rota düzeyinde veriliyor. Dört soruluk bir karar ağacı, ölçüm komutları ve en sık yapılan üç render hatası.

Bir sayfayı sunucuda mı çizmeli, tarayıcıda mı? Soru moda tartışması gibi görünüyor ama cevabı üç ölçüye bakınca çoğu zaman kendiliğinden çıkıyor: veri kime ait, içerik ne kadar sık değişiyor, ilk boyamadan sonra ne oluyor.

Önce yanlış soruyu eleyelim

"SSR mi CSR mi" ikilisi bugünün araçlarında zaten sahte bir ikilem. Modern çatıların hepsinde aynı uygulama içinde sayfa sayfa, hatta bileşen bileşen karar verilebiliyor. Yani karar uygulamanın tamamı için bir kez verilmiyor; her rota için ayrı veriliyor. Karar ağacını da bu yüzden rota düzeyinde kurmak gerekiyor.

Dört seçenek var, ve aralarındaki fark HTML'in ne zaman üretildiğiyle ilgili:

  • Statik (SSG): HTML derleme anında üretiliyor, CDN'den servis ediliyor.
  • Sunucuda istek anında (SSR): HTML her istekte üretiliyor.
  • Artımlı yeniden üretim (ISR): Statik üretiliyor, belirli aralıklarla arka planda tazeleniyor.
  • İstemcide (CSR): Sunucu boş bir kabuk gönderiyor, veri tarayıcıda çekiliyor.

Karar ağacı

Sırayla sorun, ilk "evet"te durun.

1. İçerik kullanıcıya özel mi? Oturum açmış kullanıcının panosu, sepeti, mesaj kutusu. Bu içerik önbelleğe alınamıyor, arama motorunun da görmesi gerekmiyor. Kabuğu statik verin, veriyi istemcide çekin. Ya da sunucuda çizin ama önbelleği kapatın. Ölçü şu: bu HTML'i ikinci bir kullanıcıya gösterebilir misiniz? Cevap hayırsa önbellek yok demektir.

2. Arama motorunun ya da bağlantı önizlemesinin görmesi gerekiyor mu? Ürün sayfası, blog yazısı, kategori listesi. Evetse HTML sunucudan gelmeli. Burada CSR'ye gitmek, tarayıcı JavaScript'i çalıştırana kadar sayfayı boş bırakıyor; paylaşım önizlemeleri de çoğu zaman boş çıkıyor.

3. İçerik istekten isteğe değişiyor mu? Değişmiyorsa statik. Değişiyor ama saatlik gecikme kabulse ISR. Saniyelik güncellik şartsa SSR.

4. Sayfanın çoğu kısmı sabit, küçük bir parçası mı kişisel? Bu en sık karşılaşılan durum ve en sık yanlış çözülen durum. Bütün sayfayı kişisel diye SSR'ye çevirmek yerine, sayfayı statik üretip yalnız o parçayı istemcide doldurmak doğru olanı.

// Yanlış: tek bir "Merhaba Ayşe" için tüm sayfa istek anında üretiliyor
export const dynamic = "force-dynamic";

export default async function Page() {
  const urunler = await urunleriGetir();     // herkes için aynı
  const kullanici = await oturumuGetir();    // kişiye özel
  return <Liste urunler={urunler} isim={kullanici.isim} />;
}
// Doğru: gövde statik, kişisel parça istemcide
export const revalidate = 3600;

export default async function Page() {
  const urunler = await urunleriGetir();
  return (
    <>
      <Karsilama />        {/* "use client", oturumu kendi çekiyor */}
      <Liste urunler={urunler} />
    </>
  );
}

Bu ayrımın maliyeti bir bileşen sınırı. Kazancı, sayfanın CDN'de kalabilmesi.

Ölçmeden karar vermeyin

Karar ağacı bir başlangıç noktası. Gerçek cevap ölçümde. İki sayıya bakın:

# 1) HTML'in kendisi ne kadar sürede geliyor (TTFB)
curl -o /dev/null -s -w 'ttfb=%{time_starttransfer}s toplam=%{time_total}s\n' \
  https://site.example/urun/123
// 2) İlk anlamlı içerik ne zaman boyanıyor
new PerformanceObserver((list) => {
  for (const e of list.getEntries()) console.log(e.name, e.startTime);
}).observe({ type: "largest-contentful-paint", buffered: true });

SSR'de TTFB yükselir, LCP genelde düşer. CSR'de TTFB neredeyse sıfırdır ama LCP, JavaScript paketi indirilip çalıştırıldıktan ve veri isteği döndükten sonra gerçekleşir. Yani CSR'de kullanıcı "hızlı" bir boş sayfa görüyor. Hangisinin daha iyi olduğu sayfanın işine bağlı: ödeme akışında boş kabuk kabul edilebilir, ürün sayfasında değil.

Sık yapılan üç hata

Veriyi iki kez çekmek. Sunucuda çizilen sayfa, aynı veriyi istemcide bir daha istiyor. Ağ sekmesinde aynı uç noktaya iki istek görüyorsanız durum budur. Genelde sebebi, sunucudan gelen veriyi başlangıç durumu olarak kullanmayan bir veri katmanı.

Her şeyi dinamik yapmak. Bir yerde tarih ya da rastgele değer kullanıldığı için tüm rota istek anına düşüyor. Çatıların çoğu bunu sessizce yapıyor; derleme çıktısındaki rota tablosunu okumak tek uyarı.

Şelale isteği. Sunucuda sıralı üç await, üç ağ turu demek. Birbirine bağımlı değillerse tek seferde toplayın:

const [urun, yorumlar, stok] = await Promise.all([
  urunGetir(id), yorumlariGetir(id), stokGetir(id),
]);

Kararın görünmeyen tarafı: maliyet ve dayanıklılık

Render kararı yalnız hız meselesi değil. İstek anında çizilen her sayfa, sunucuda çalışan bir işlev demek; trafik artınca fatura da doğrusal artıyor. Statik sayfa ise CDN'de duruyor, trafikle birlikte maliyeti aynı oranda büyümüyor.

Dayanıklılık tarafı daha az konuşuluyor ama etkisi daha sert. Sunucuda çizilen bir sayfada veri kaynağı yavaşlarsa kullanıcı bekleyen bir sayfa görüyor; kaynak düşerse sayfanın tamamı düşüyor. Aynı sayfa statik üretilmiş olsaydı, veri kaynağı düşse bile son üretilen sürüm servis edilmeye devam edecekti. Kampanya günlerinde bu fark, "site yavaş" ile "site kapalı" arasındaki fark oluyor. Bu yüzden kritik sayfalarda tercih genelde ISR'den yana yapılıyor: bayat veri, hiç veri olmamasından iyidir.

Pratik varsayılan

Yeni bir rota açarken şu sırayla ilerlemek çoğu projede işi görüyor: önce statik dene, güncellik yetmiyorsa ISR'ye geç, kişiselleşme varsa o parçayı istemciye ayır, ancak hiçbiri olmuyorsa tüm rotayı istek anına al. Ters yönden başlamak, yani her şeyi dinamik kurup sonra optimize etmeye çalışmak, sonradan sökülmesi en zor bağımlılıkları üretiyor.