React Server Components Olgunlaşması: "use client" ve "use server" Paradigmasını Derinlemesine Anlamak
React ekosistemi, son birkaç yılda belki de tarihinin en büyük paradigma değişimini yaşıyor. React Server Components (RSC), ilk duyurulduğu 2020 yılından bu yana uzun bir olgunlaşma süreci geçirdi. React 19 ile birlikte artık kararlı (stable) hale gelen bu mimari, "use client" ve "use server" direktifleri etrafında şekilleniyor. Peki bu iki basit görünümlü satır, React geliştirme deneyimimizi nasıl dönüştürüyor?
Eski Dünya: Her Şey İstemcide
Klasik React uygulamalarında tüm bileşenler istemci tarafında çalışıyordu. Bir veri çekmeniz gerektiğinde useEffect içinde bir API çağrısı yapıyor, loading state yönetiyor ve sonucu render ediyordunuz:
// Eski yaklaşım - Her şey istemcide
import { useState, useEffect } from 'react';
function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/products')
.then(res => res.json())
.then(data => {
setProducts(data);
setLoading(false);
});
}, []);
if (loading) return <Spinner />;
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name} - {p.price}₺</li>
))}
</ul>
);
}Bu yaklaşımın sorunları açıktı: büyük JavaScript bundle'ları, istemcide gereksiz veri çekme waterfall'ları, SEO zorlukları ve kullanıcıya boş bir sayfa gösterildikten sonra içeriğin yüklenmesini bekleme. Server Components, bu sorunların tamamını köküne inerek çözmeyi hedefliyor.
Server Components Nedir?
React Server Components, sunucu üzerinde çalışan ve istemciye JavaScript göndermeyen React bileşenleridir. HTML döndürmezler; bunun yerine React'in anlayabileceği özel bir serileştirilmiş format (RSC Payload) üretirler. Bu payload, istemcideki React ağacıyla birleştirilir.
Temel fark şudur: Server Component'ler useState, useEffect gibi istemci hook'larını kullanamazlar. Bunun yerine doğrudan veritabanına erişebilir, dosya sistemiyle çalışabilir ve async/await kullanabilirler.
// app/products/page.jsx — Bu bir Server Component (varsayılan)
import { db } from '@/lib/database';
async function ProductPage() {
// Doğrudan veritabanından veri çekiyoruz — API katmanı yok!
const products = await db.product.findMany({
orderBy: { createdAt: 'desc' },
take: 20,
});
return (
<main>
<h1>Ürünlerimiz</h1>
<ul>
{products.map(product => (
<li key={product.id}>
<h2>{product.name}</h2>
<p>{product.description}</p>
<span className="price">{product.price}₺</span>
</li>
))}
</ul>
</main>
);
}
export default ProductPage;Dikkat edin: useEffect yok, loading state yok, API route yok. Bileşen sunucuda çalışıyor, veritabanıyla doğrudan konuşuyor ve istemciye sıfır JavaScript gönderiyor.
"use client" Direktifi: İstemci Sınırını Çizmek
Next.js App Router'da (ve RSC destekleyen diğer framework'lerde) tüm bileşenler varsayılan olarak Server Component'tir. Bir bileşenin istemcide çalışması gerektiğinde, dosyanın en üstüne "use client" direktifini eklemeniz gerekir.
'use client';
import { useState } from 'react';
function AddToCartButton({ productId }) {
const [isAdding, setIsAdding] = useState(false);
const [quantity, setQuantity] = useState(1);
const handleAddToCart = async () => {
setIsAdding(true);
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId, quantity }),
});
setIsAdding(false);
};
return (
<div>
<select value={quantity} onChange={e => setQuantity(Number(e.target.value))}>
{[1, 2, 3, 4, 5].map(n => (
<option key={n} value={n}>{n}</option>
))}
</select>
<button onClick={handleAddToCart} disabled={isAdding}>
{isAdding ? 'Ekleniyor...' : 'Sepete Ekle'}
</button>
</div>
);
}
export default AddToCartButton;Ne Zaman "use client" Kullanmalısınız?
"use client" direktifini yalnızca şu durumlarda kullanmalısınız:
- State yönetimi:
useState,useReducerkullanmanız gerektiğinde - Yaşam döngüsü efektleri:
useEffect,useLayoutEffectgerektiğinde - Tarayıcı API'leri:
window,document,localStorageerişimi gerektiğinde - Event handler'lar:
onClick,onChangegibi kullanıcı etkileşimleri olduğunda - Üçüncü parti kütüphaneler: Yalnızca istemcide çalışan kütüphaneler (animasyon kütüphaneleri, bazı form kütüphaneleri vb.)
Kritik Kural: "use client" Bir Sınır Çizer
"use client" yalnızca o dosyayı değil, o dosyadan import edilen tüm bileşenleri de istemci bileşeni yapar. Bu nedenle, istemci sınırınızı mümkün olduğunca bileşen ağacının yaprak düğümlerine (leaf nodes) yakın tutmalısınız.
// ❌ KÖTÜ: Tüm sayfayı istemci bileşeni yapmak
'use client';
function ProductPage() { ... }
// ✅ İYİ: Sadece interaktif kısmı istemci bileşeni yapmak
// ProductPage → Server Component
// └── ProductDetails → Server Component
// └── AddToCartButton → 'use client'"use server" Direktifi: Server Actions
"use server" direktifi, Server Actions tanımlamak için kullanılır. Server Actions, istemciden çağrılabilen ancak sunucuda çalışan asenkron fonksiyonlardır. Form gönderimi, veri mutasyonu ve diğer sunucu taraflı işlemler için tasarlanmıştır.
Dosya Düzeyinde Kullanım
Bir dosyanın en üstüne "use server" ekleyerek, o dosyadaki tüm export edilen fonksiyonları Server Action haline getirebilirsiniz:
// app/actions/product.js
'use server';
import { db } from '@/lib/database';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createProduct(formData) {
const name = formData.get('name');
const price = parseFloat(formData.get('price'));
const description = formData.get('description');
// Validasyon
if (!name || !price) {
return { error: 'İsim ve fiyat zorunludur.' };
}
// Veritabanına kaydet
await db.product.create({
data: { name, price, description },
});
// Cache'i temizle ve yönlendir
revalidatePath('/products');
redirect('/products');
}
export async function deleteProduct(productId) {
await db.product.delete({
where: { id: productId },
});
revalidatePath('/products');
}Server Actions'ı Form ile Kullanmak
Server Actions, HTML <form> elementinin action prop'una doğrudan geçirilebilir. Bu, progresif iyileştirme (progressive enhancement) sağlar — JavaScript devre dışı olsa bile form çalışır:
// app/products/new/page.jsx — Server Component
import { createProduct } from '@/app/actions/product';
export default function NewProductPage() {
return (
<form action={createProduct}>
<div>
<label htmlFor="name">Ürün Adı</label>
<input type="text" id="name" name="name" required />
</div>
<div>
<label htmlFor="price">Fiyat (₺)</label>
<input type="number" id="price" name="price" step="0.01" required />
</div>
<div>
<label htmlFor="description">Açıklama</label>
<textarea id="description" name="description" rows={4} />
</div>
<button type="submit">Ürün Oluştur</button>
</form>
);
}İstemci Bileşenlerinde Server Actions
Server Actions, istemci bileşenlerinde de kullanılabilir. React 19 ile gelen useActionState hook'u bu entegrasyonu zarif bir şekilde sağlar:
'use client';
import { useActionState } from 'react';
import { createProduct } from '@/app/actions/product';
function ProductForm() {
const [state, formAction, isPending] = useActionState(createProduct, null);
return (
<form action={formAction}>
{state?.error && (
<div className="error-banner">{state.error}</div>
)}
<input type="text" name="name" placeholder="Ürün adı" />
<input type="number" name="price" placeholder="Fiyat" step="0.01" />
<textarea name="description" placeholder="Açıklama" />
<button type="submit" disabled={isPending}>
{isPending ? 'Kaydediliyor...' : 'Kaydet'}
</button>
</form>
);
}Bileşen Kompozisyon Desenleri
RSC mimarisinin en güçlü yanlarından biri, Server ve Client Component'lerin birlikte çalışma biçimidir.
Desen 1: Server Component İçinde Client Component
Bu en yaygın ve doğal desendir:
// app/products/[id]/page.jsx — Server Component
import { db } from '@/lib/database';
import AddToCartButton from '@/components/AddToCartButton';
import ProductImageGallery from '@/components/ProductImageGallery';
export default async function ProductDetailPage({ params }) {
const { id } = await params;
const product = await db.product.findUnique({ where: { id } });
return (
<article>
<h1>{product.name}</h1>
<p className="price">{product.price}₺</p>
<p>{product.description}</p>
{/* Client Components — sadece interaktif kısımlar */}
<ProductImageGallery images={product.images} />
<AddToCartButton productId={product.id} />
</article>
);
}Desen 2: Children Prop ile Server Component'i Client Component'e Geçirmek
Bir Client Component, children prop'u aracılığıyla Server Component'leri render edebilir:
'use client';
import { useState } from 'react';
export function Accordion({ title, children }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="accordion">
<button onClick={() => setIsOpen(!isOpen)}>
{title} {isOpen ? '▼' : '▶'}
</button>
{isOpen && <div className="accordion-content">{children}</div>}
</div>
);
}// Server Component'de kullanım
import { Accordion } from '@/components/Accordion';
import { db } from '@/lib/database';
export default async function FAQPage() {
const faqs = await db.faq.findMany();
return (
<div>
{faqs.map(faq => (
<Accordion key={faq.id} title={faq.question}>
{/* Bu içerik Server Component olarak render edilir */}
<p>{faq.answer}</p>
</Accordion>
))}
</div>
);
}Yaygın Hatalar ve Çözümleri
1. Server Component'e Hook Eklemeye Çalışmak
// ❌ HATA: Server Component'te useState kullanılamaz
async function SearchPage() {
const [query, setQuery] = useState(''); // Bu hata verir!
return <input onChange={e => setQuery(e.target.value)} />;
}
// ✅ ÇÖZÜM: Arama input'unu ayrı bir Client Component yapın2. Server Component'e Serileştirilemez Prop Geçirmek
// ❌ HATA: Fonksiyonlar Client Component'e prop olarak geçirilemez
// (Server Actions hariç)
<ClientComponent onCustomEvent={() => console.log('test')} />
// ✅ ÇÖZÜM: Server Action kullanın veya mantığı Client Component'e taşıyın3. "use client" Sınırını Çok Yukarıda Tanımlamak
// ❌ KÖTÜ: Layout seviyesinde 'use client'
'use client';
export default function Layout({ children }) {
const [theme, setTheme] = useState('light');
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}
// ✅ İYİ: Sadece provider'ı Client Component yapın
// ThemeProvider.jsx → 'use client'
// Layout.jsx → Server Component, ThemeProvider'ı import ederPerformans Etkisi: Gerçek Dünyadan Rakamlar
Server Components'in performans üzerindeki etkisi somut ve ölçülebilirdir:
| Metrik | Geleneksel CSR | RSC ile |
|---|---|---|
| İlk JavaScript Bundle | ~250KB+ | ~80KB (sadece client bileşenler) |
| Time to First Byte | Yavaş (boş HTML) | Hızlı (içerik dolu HTML) |
| Largest Contentful Paint | ~3-4s | ~1-2s |
| Veri Çekme Waterfall | İstemcide çoklu | Sunucuda tek geçiş |
Sonuç
React Server Components, "use client" ve "use server" direktifleri ile birlikte React'in geleceğini şekillendiriyor. Bu paradigma değişimi ilk bakışta karmaşık görünebilir, ancak temeldeki fikir oldukça basittir:
- Varsayılan olarak Server Component kullanın. Bileşenleriniz sunucuda çalışsın, sıfır JavaScript göndersin.
- "use client" direktifini yalnızca etkileşim gerektiğinde ekleyin. İstemci sınırını mümkün olduğunca küçük tutun.
- "use server" ile Server Actions tanımlayarak form gönderimlerini ve veri mutasyonlarını güvenli ve temiz bir şekilde yönetin.
- Kompozisyon desenlerini öğrenin. Children prop'u ve doğru bileşen sınırları, performanslı ve sürdürülebilir uygulamaların anahtarıdır.
React 19'un kararlı sürümüyle birlikte RSC artık deneysel bir özellik değil, React'in birincil mimari modelidir. Şimdi bu paradigmayı öğrenmek ve benimsemek, gelecekte yazacağınız her React uygulaması için size güçlü bir temel sağlayacaktır.