Next.js Server Components vs Client Components

Next.js Server Components vs Client Components
React Server Components (RSC), React ve Next.js ekosistemindeki en önemli paradigma değişikliklerinden biridir. Next.js App Router'da bileşenler varsayılan olarak Server Component'tir. Bu yaklaşım, sunucu tarafında render edilen bileşenlerle istemci tarafında çalışan bileşenleri net bir şekilde ayırarak performans ve geliştirici deneyimini büyük ölçüde iyileştirir.
Server Components Nedir?
Server Components, sunucu tarafında render edilen ve JavaScript bundle'ına dahil edilmeyen React bileşenleridir. Bu bileşenler doğrudan veritabanına erişebilir, dosya sistemiyle çalışabilir ve hassas API anahtarlarını güvenli şekilde kullanabilir.
// Bu bir Server Component (varsayılan)
// 'use client' direktifi YOK
import { db } from '@/lib/database';
export default async function ProductList() {
// Doğrudan veritabanına erişim
const products = await db.product.findMany({
orderBy: { createdAt: 'desc' },
take: 20,
});
return (
<div className="grid grid-cols-3 gap-4">
{products.map((product) => (
<div key={product.id} className="border rounded-lg p-4">
<h3 className="font-bold">{product.name}</h3>
<p className="text-gray-600">{product.price} TL</p>
</div>
))}
</div>
);
}
Client Components Nedir?
Client Components, tarayıcıda çalışan ve kullanıcı etkileşimi gerektiren bileşenlerdir. 'use client' direktifi ile tanımlanır:
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div className="flex items-center gap-4">
<button
onClick={() => setCount(count - 1)}
className="px-4 py-2 bg-red-500 text-white rounded"
>
-
</button>
<span className="text-2xl font-bold">{count}</span>
<button
onClick={() => setCount(count + 1)}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
+
</button>
</div>
);
}
'use client' direktifi bir sınır (boundary) oluşturur. Bu direktifin altındaki bileşen ve onun import ettiği tüm bileşenler client bundle'ına dahil edilir.
Ne Zaman Hangisini Kullanmalı?
| Senaryo | Server Component | Client Component |
|---|---|---|
| Veri çekme (fetch) | ✅ Tercih edilir | Mümkün ama ideal değil |
| Veritabanı erişimi | ✅ Doğrudan | ❌ API üzerinden |
| onClick, onChange | ❌ Kullanamaz | ✅ Gereklidir |
| useState, useEffect | ❌ Kullanamaz | ✅ Kullanabilir |
| Hassas bilgiler (API key) | ✅ Güvenli | ❌ Açığa çıkar |
| Büyük bağımlılıklar | ✅ Bundle'a eklenmez | ❌ Bundle'ı büyütür |
Composition Pattern: Server ve Client Birlikte
En iyi uygulama, Server Component'lerin Client Component'leri sarmasıdır. Veri Server Component'te çekilir, etkileşimli parçalar Client Component'e aktarılır:
// app/products/page.tsx (Server Component)
import { db } from '@/lib/database';
import { ProductFilter } from './ProductFilter';
import { ProductCard } from './ProductCard';
export default async function ProductsPage() {
const products = await db.product.findMany();
const categories = await db.category.findMany();
return (
<div>
<h1>Ürünler</h1>
{/* Client Component - filtreleme etkileşimi */}
<ProductFilter categories={categories} />
{/* Server Component - sadece görüntüleme */}
<div className="grid grid-cols-3 gap-4">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
</div>
);
}
// ProductFilter.tsx (Client Component)
'use client';
import { useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
export function ProductFilter({ categories }: { categories: Category[] }) {
const router = useRouter();
const searchParams = useSearchParams();
const [selected, setSelected] = useState(
searchParams.get('category') || 'all'
);
const handleFilter = (category: string) => {
setSelected(category);
router.push(`/products?category=${category}`);
};
return (
<div className="flex gap-2 my-4">
{categories.map((cat) => (
<button
key={cat.id}
onClick={() => handleFilter(cat.slug)}
className={selected === cat.slug ? 'bg-blue-500 text-white' : 'bg-gray-200'}
>
{cat.name}
</button>
))}
</div>
);
}
Children Pattern ile Doğru Kullanım
// ❌ Yanlış: Server Component, Client import'u içinde
'use client';
import ServerComponent from './ServerComponent'; // Client olarak çalışır!
export function ClientWrapper() {
return <div><ServerComponent /></div>;
}
// ✅ Doğru: Children pattern
'use client';
export function ClientWrapper({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(true);
return isOpen ? <div>{children}</div> : null;
}
// page.tsx (Server Component)
import { ClientWrapper } from './ClientWrapper';
import { ServerComponent } from './ServerComponent';
export default function Page() {
return (
<ClientWrapper>
<ServerComponent /> {/* Server Component olarak kalır */}
</ClientWrapper>
);
}
Async Server Components
Server Components async olabilir, bu da veri çekmeyi son derece basitleştirir:
// Async Server Component
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`, {
cache: 'no-store', // Her istekte taze veri
});
return res.json();
}
export default async function UserProfile({
params,
}: {
params: { id: string };
}) {
const user = await getUser(params.id);
return (
<div className="max-w-2xl mx-auto p-6">
<h1 className="text-3xl font-bold">{user.name}</h1>
<p className="text-gray-600">{user.email}</p>
<p className="mt-4">{user.bio}</p>
</div>
);
}
Serialization Kuralları
Server Component'ten Client Component'e prop geçerken, veriler serializable olmalıdır:
// ✅ Geçerli prop türleri
// string, number, boolean, null, undefined
// Array, Object (serializable)
// Date (string'e çevrilir)
// Map, Set — ❌ Geçersiz
// Fonksiyonlar — ❌ Geçersiz
// Class instance'ları — ❌ Geçersiz
// ✅ Doğru
<ClientComponent data={{ name: 'Tarik', age: 30 }} />
// ❌ Yanlış
<ClientComponent onClick={() => console.log('click')} />
// Fonksiyonlar serialize edilemez!
Third-Party Kütüphanelerle Çalışma
Birçok React kütüphanesi henüz Server Components ile uyumlu değildir. Bu durumda wrapper bileşen oluşturabilirsiniz:
// components/ThemeProvider.tsx
'use client';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
export function ThemeProvider({ children }: { children: React.ReactNode }) {
return (
<NextThemesProvider attribute="class" defaultTheme="system">
{children}
</NextThemesProvider>
);
}
// app/layout.tsx (Server Component)
import { ThemeProvider } from '@/components/ThemeProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="tr">
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
Sonuç
Server Components ve Client Components ayrımı, Next.js uygulamalarında performans ve güvenlik açısından büyük avantajlar sağlar. Temel kural basittir: mümkün olduğunca Server Component kullanın, yalnızca etkileşim gerektiğinde Client Component'e geçin. Composition pattern'lerini doğru uygulayarak her iki dünyanın en iyisinden faydalanabilirsiniz.