Next.js App Router ile bir proje açtığınızda artık varsayılan olarak Next.js Server Components dünyasındasınız. Eskiden alışkın olduğumuz "her şey tarayıcıda çalışan React bileşeni" modeli yerini, bir kısmı sunucuda render edilen bir kısmı tarayıcıya inen ikili bir yapıya bıraktı. Bu yazıda Server Components ile Client Components arasındaki gerçek farkı, ikisinin nasıl bir arada çalıştığını ve günlük işte hangisini ne zaman seçeceğinizi somut örneklerle anlatıyorum.
Server Components ve Client Components nedir?
İki tür arasındaki ayrım, bileşenin nerede çalıştığıyla ilgilidir.
- Server Component (RSC): Yalnızca sunucuda çalışır. Çıktısı tarayıcıya HTML ve özel bir seri hale getirilmiş yük olarak iner; bileşenin JavaScript'i istemciye gönderilmez. Veritabanına, dosya sistemine veya gizli API anahtarlarına doğrudan erişebilir.
- Client Component: Sunucuda bir kez render edilip ardından tarayıcıda "hydrate" edilir (interaktif hale gelir).
useState,useEffect, olay dinleyicileri ve tarayıcı API'leri burada çalışır.
App Router'da her bileşen varsayılan olarak bir Server Component'tir. Bir bileşeni Client Component yapmak için dosyanın en üstüne "use client" direktifini eklersiniz.
"use client" gerçekte ne yapar?
Yaygın bir yanlış anlama, "use client" yazılan dosyanın "sadece tarayıcıda" çalıştığıdır. Aslında o bileşen yine de ilk istekte sunucuda render edilir; direktifin işlevi, o dosyadan başlayan istemci sınırını (client boundary) tanımlamaktır. Bu sınırdan itibaren bileşen ve onun import ettiği şeyler istemci paketine dahil edilir.
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Sayaç: {count}
</button>
);
}
Direktif yalnızca dosyanın en başında, herhangi bir import'tan önce yer almalıdır. Bir kez bir Client Component sınırına girdiğinizde, onun içinden render edilen tüm alt bileşenler de istemci tarafında değerlendirilir.
Hangisini ne zaman seçmeli?
Pratik kural basittir: varsayılanı Server Component olarak bırakın, yalnızca etkileşim veya tarayıcı API'si gerektiğinde Client'a geçin.
Server Component tercih edin şu durumlarda:
- Veritabanından veya bir API'den veri çekiyorsanız (
async/awaitile doğrudan). - Gizli tutulması gereken anahtar, token veya bağlantı dizesi kullanıyorsanız.
- Büyük bir bağımlılığı (örneğin Markdown ayrıştırıcı veya tarih kütüphanesi) istemci paketinden uzak tutmak istiyorsanız.
- İçerik büyük ölçüde statikse ve etkileşim yoksa.
Client Component tercih edin şu durumlarda:
useState,useReducergibi state veyauseEffectgibi yaşam döngüsü gerekiyorsa.onClick,onChangegibi olay dinleyicileri kullanıyorsanız.window,localStorage,IntersectionObservergibi tarayıcıya özgü API'lere ihtiyacınız varsa.- Yalnızca istemcide çalışan bir kütüphane (örneğin birçok animasyon veya harita bileşeni) kullanıyorsanız.
İkisini birlikte kullanmak: kompozisyon kalıbı
Asıl güç, ikisini iç içe geçirmekte. Yaygın ve önerilen kalıp, sunucuda veri çekip onu küçük bir Client Component'e prop olarak geçirmektir. Böylece interaktif kısmı küçük tutar, ağır işi sunucuda bırakırsınız.
// app/page.tsx (Server Component)
import Counter from "./counter";
export default async function Page() {
const data = await getData(); // sunucuda çalışır
return (
<main>
<h1>{data.title}</h1>
<Counter /> {/* istemci sınırı */}
</main>
);
}
Önemli bir ayrıntı: bir Server Component'i bir Client Component'in içine import edip render edemezsiniz, ama onu children olarak geçirebilirsiniz. Bu sayede bir Client sarmalayıcının (örneğin bir tema sağlayıcısı) içinde yine Server Component içeriği gösterebilirsiniz:
// Client Component
"use client";
export default function Panel({ children }) {
return <div className="panel">{children}</div>;
}
// Server Component
<Panel>
<ServerContent /> {/* children olarak geçer, sunucuda kalır */}
</Panel>
Sık yapılan hatalar
- Her şeye
"use client"koymak. Bu, RSC'nin tüm avantajını kaybettirir; paket büyür, ilk yük yavaşlar. Direktifi mümkün olduğunca yaprak (en alttaki interaktif) bileşene yakın koyun. - Server Component içinde
useStateveya olay handler kullanmaya çalışmak. Bu derleme/çalışma zamanı hatası verir; bileşeni Client yapmanız gerekir. - Gizli anahtarı Client Component'e taşımak. İstemci paketine giren her şey herkese görünür. Sırlar yalnızca Server Component'lerde veya sunucu kodunda kalmalı.
asyncClient Component yazmak. Veri çekmek için Server Component'leriasyncyapabilirsiniz; Client Component'lerde veri çekmek içinuseEffectveya bir kütüphane (örneğin SWR) kullanın.
Sık Sorulan Sorular
Server Components, getServerSideProps'un yerini mi aldı?
Büyük ölçüde evet. App Router'da artık getServerSideProps veya getStaticProps yoktur; veriyi doğrudan async bir Server Component içinde await ile çekersiniz. Önbellekleme ve yeniden doğrulama davranışını ise fetch seçenekleriyle kontrol edersiniz.
Bir Client Component, Server Component'i çocuk olarak alabilir mi?
Evet. Bir Client Component, Server Component'i doğrudan import edip render edemez ama children ya da başka bir prop aracılığıyla alabilir. Bu, etkileşimli sarmalayıcılar ile sunucu içeriğini birleştirmenin standart yoludur.
Server Component'te neden useState çalışmıyor?
Çünkü useState tarayıcıda yaşayan, render'lar arasında korunan bir state gerektirir; Server Component'in istemci tarafında bir yaşam döngüsü yoktur. State veya etkileşim gerektiğinde o parçayı bir Client Component'e ayırın.
Next.js mimarinizi doğru kurgulamak mı istiyorsunuz? Server ve Client Components'ı dengeli kullanan, hızlı ve sürdürülebilir bir uygulama için birlikte çalışabiliriz. Benimle iletişime geçin.