REACT10m READ11 Haziran 2026

React Server Components vs Client Components: Ne Zaman Hangisi?

RSC mimarisi, bundle size etkisi ve bileşen sınır kararları.

Next.js 13+ ile gelen React Server Components, React uygulamalarında component'ların nerede çalıştığı konusundaki temel varsayımı değiştirdi. Artık her bileşen ya sunucuda ya da tarayıcıda çalışır; ikisi arasındaki sınır doğru çizildiğinde hem performans hem de geliştirici deneyimi kazanır.

Temel Fark

// PLAINTEXT //
Server Component                    Client Component
──────────────────────────────────────────────────
❌ useState, useEffect              ✅ useState, useEffect
❌ Browser API'leri (window, DOM)   ✅ Browser API'leri
❌ Event handler'lar                ✅ Event handler'lar
✅ async/await doğrudan             ❌ useEffect içinde fetch
✅ Veritabanı erişimi               ❌ API üzerinden erişmeli
✅ .env secret'larına erişim        ❌ Yalnızca NEXT_PUBLIC_ değişkenler
✅ Bundle'a katkısı yok             ❌ Bundle size artırır

Ne Zaman Server Component?

// TSX //
// ✅ Server Component — veri getirme, statik render
async function BlogListesi() {
  // Doğrudan DB — API round-trip yok, SECRET erişilebilir
  const makaleler = await db.makale.findMany({ where: { yayinlandi: true } });
 
  return (
    <ul>
      {makaleler.map(m => (
        // MakaleKart client component olabilir — interactivity için
        <MakaleKart key={m.id} makale={m} />
      ))}
    </ul>
  );
}
 
// ✅ Server Component — layout, navigasyon, statik UI
async function Layout({ children }: { children: React.ReactNode }) {
  const kullanici = await mevcutKullanici(); // Çerez okuma
  return (
    <div>
      <Header kullanici={kullanici} />
      <main>{children}</main>
      <Footer />
    </div>
  );
}

Ne Zaman Client Component?

// TSX //
'use client'; // Bu direktif olmadan → Server Component
 
// ✅ Client Component — interactivity gerekli
import { useState } from 'react';
 
function BegeniButonu({ makaleSlugi }: { makaleSlugi: string }) {
  const [begenildi, setBegendi] = useState(false);
  const [sayi, setSayi]         = useState(0);
 
  async function begeniToggle() {
    setBegendi(!begenildi);
    setSayi(prev => begenildi ? prev - 1 : prev + 1);
    await fetch(`/api/begeni/${makaleSlugi}`, { method: 'POST' });
  }
 
  return (
    <button onClick={begeniToggle}>
      {begenildi ? '❤️' : '🤍'} {sayi}
    </button>
  );
}
 
// ✅ Client Component — tarayıcı API'si
function KaranlikModButonu() {
  const [koyu, setKoyu] = useState(
    () => window.matchMedia('(prefers-color-scheme: dark)').matches
  );
 
  useEffect(() => {
    document.documentElement.classList.toggle('dark', koyu);
    localStorage.setItem('tema', koyu ? 'koyu' : 'acik');
  }, [koyu]);
 
  return <button onClick={() => setKoyu(!koyu)}>Tema Değiştir</button>;
}

Bileşen Sınırını Doğru Çizmek

// TSX //
// ❌ Yanlış — tüm sayfa client component oldu
'use client';
async function BlogSayfasi() { // async client component = hata!
  const [filtre, setFiltre] = useState('');
  const makaleler = await db.makale.findMany(); // ❌ Client'ta DB erişimi yok
  // ...
}
 
// ✅ Doğru — sınırı mümkün olduğunca aşağı it
// page.tsx — Server Component (varsayılan)
async function BlogSayfasi() {
  const makaleler = await db.makale.findMany({ where: { yayinlandi: true } });
  return (
    <div>
      <AramaFiltresi />         {/* Client — interactivity */}
      <MakaleListesi makaleler={makaleler} /> {/* Server — statik */}
    </div>
  );
}
 
// components/AramaFiltresi.tsx
'use client';
function AramaFiltresi() {
  const [filtre, setFiltre] = useState('');
  const router = useRouter();
 
  function ara(e: React.FormEvent) {
    e.preventDefault();
    router.push(`/blog?q=${filtre}`);
  }
 
  return (
    <form onSubmit={ara}>
      <input value={filtre} onChange={e => setFiltre(e.target.value)} />
    </form>
  );
}

Server Component'tan Client Component'a Veri Aktarma

// TSX //
// Server → Client: sadece serileştirilebilir prop'lar
async function MakaleSayfasi({ params }: { params: { slug: string } }) {
  const makale = await db.makale.findUnique({ where: { slug: params.slug } });
 
  return (
    <article>
      <h1>{makale.baslik}</h1>
      {/* ✅ Düz veri aktar */}
      <BegeniButonu
        makaleSlugi={makale.slug}
        baslangiçSayisi={makale.begeniSayisi}
      />
      {/* ❌ Class instance, function, Promise geçiremezsiniz */}
    </article>
  );
}

Bundle Size Farkı

Bir bileşeni Server Component yaparak:

  • Bileşenin kodu tarayıcıya gönderilmez
  • moment, lodash, büyük kütüphaneler bundle'a girmez
  • İlk yükleme daha hızlı; Time to Interactive düşer
// PLAINTEXT //
// react-pdf gibi ağır kütüphane — Server Component olarak kullanın
async function PDFOnizleme({ url }: { url: string }) {
  const pdf = await parsePDF(url); // ağır kütüphane, bundle'a girmez
  return <div>{pdf.metin.substring(0, 200)}...</div>;
}

Karar Ağacı

// PLAINTEXT //
Bileşen useState/useEffect/event kullanıyor mu?
├── Evet → 'use client' ekle → Client Component
└── Hayır → Server Component (varsayılan)
            ├── DB/secret erişiyor mu? → Server Component ✅
            ├── async veri çekiyor mu? → Server Component ✅
            └── Statik HTML mi? → Server Component ✅

Özet

Server Components: veri çekme, statik render, ağır kütüphane kullanımı. Client Components: interactivity, browser API, gerçek zamanlı durum. Sınırı mümkün olduğunca aşağıya, yaprak bileşenlere itin. Doğru sınır = küçük bundle + hızlı ilk yükleme + sıfır gereksiz client JavaScript.