Sunucu Bileşenleri

React Server Components

Sunucu Bileşenleri aşağıdakilerde kullanım içindir React Sunucu Bileşenleri.

Sunucu Bileşenleri, önceden, paketlemeden önce, istemci uygulamanızdan veya SSR sunucusundan ayrı bir ortamda render edilen yeni bir Bileşen türüdür.

Bu ayrı ortam, React Sunucu Bileşenlerinde “sunucu” olarak adlandırılır. Sunucu Bileşenleri, CI sunucunuzda build zamanı sırasında bir kez çalışabilir veya her istekte bir web sunucusu kullanılarak çalıştırılabilir.

Not

Sunucu Bileşenleri için nasıl destek oluşturulur?

React 19’daki React Sunucu Bileşenleri kararlı ve alt sürümler arasında kırılmayacak olsa da, bir React Sunucu Bileşenleri paketleyicisini veya framework’ünü uygulamak için kullanılan temel API’ler semver’ı takip etmez ve React 19.x’teki alt sürümler arasında kırılabilir.

React Sunucu Bileşenleri’ni bir paketleyici veya framework olarak desteklemek için, belirli bir React sürümüne sabitlemenizi veya Canary sürümünü kullanmanızı öneririz. Gelecekte, React Sunucu Bileşenleri’ni uygulamak için kullanılan API’leri stabilize etmek amacıyla paketleyiciler ve framework’lerle çalışmaya devam edeceğiz.

Sunucu Olmadan Sunucu Bileşenleri

Sunucu bileşenleri, dosya sisteminden okumak veya statik içerik almak için build zamanı sırasında çalışabilir, bu nedenle bir web sunucusu gerekmez. Örneğin, bir içerik yönetim sisteminden statik veriler okumak isteyebilirsiniz.

Sunucu Bileşenleri olmadan, statik verileri istemcide bir Efekt ile almak yaygındır:

// bundle.js
import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)

function Page({page}) {
const [content, setContent] = useState('');
// NOT: İlk sayfa render'ından *sonra* yüklenir.
useEffect(() => {
fetch(`/api/content/${page}`).then((data) => {
setContent(data.content);
});
}, [page]);

return <div>{sanitizeHtml(marked(content))}</div>;
}
// api.js
app.get(`/api/content/:page`, async (req, res) => {
const page = req.params.page;
const content = await file.readFile(`${page}.md`);
res.send({content});
});

Bu desen, kullanıcıların ek olarak 75K (gzipped) kütüphane indirip çözümlemeleri gerektiği ve sayfa yüklendikten sonra verileri almak için ikinci bir isteği beklemeleri gerektiği anlamına gelir; sadece sayfa ömrü boyunca değişmeyecek statik içeriği render etmek için.

Sunucu Bileşenleri ile, bu bileşenleri build zamanı sırasında bir kez render edebilirsiniz:

import marked from 'marked'; // Paket içinde dahil edilmemiş
import sanitizeHtml from 'sanitize-html'; // Paket içinde dahil edilmemiş

async function Page({page}) {
// NOT: Render sırasında, uygulama build edilirken yüklenir.
const content = await file.readFile(`${page}.md`);

return <div>{sanitizeHtml(marked(content))}</div>;
}

Render edilen çıktı daha sonra sunucu tarafında render edilip (SSR) HTML olarak oluşturulabilir ve bir CDN’ye yüklenebilir. Uygulama yüklendiğinde, istemci orijinal Page bileşenini veya markdown render’lamak için kullanılan pahalı kütüphaneleri görmez. İstemci yalnızca render edilmiş çıktıyı görür:

<div><!-- markdown için html --></div>

Bu, içeriğin ilk sayfa yüklemesi sırasında görünür olduğu ve paketlemenin statik içeriği render etmek için gereken pahalı kütüphaneleri içermediği anlamına gelir.

Not

Yukarıdaki Sunucu Bileşeni’nin bir async fonksiyon olduğunu fark etmiş olabilirsiniz:

async function Page({page}) {
//...
}

Async Bileşenleri, render sırasında await yapmanıza olanak tanıyan Sunucu Bileşenleri’nin yeni bir özelliğidir.

Aşağıda Sunucu Bileşenleri ile Async Bileşenleri başlığına bakın.

Sunucu ile Sunucu Bileşenleri

Sunucu Bileşenleri, bir sayfa isteği sırasında bir web sunucusunda da çalışabilir, böylece bir API oluşturmanıza gerek kalmadan veri katmanınıza erişmenizi sağlar. Uygulamanız paketlenmeden önce render edilirler ve veri ile JSX’i İstemci Bileşenlerine prop olarak geçirebilirler.

Sunucu Bileşenleri olmadan, dinamik verileri istemcide bir Efekt ile almak yaygındır:

// bundle.js
function Note({id}) {
const [note, setNote] = useState('');
// NOT: İlk render'dan *sonra* yüklenir.
useEffect(() => {
fetch(`/api/notes/${id}`).then(data => {
setNote(data.note);
});
}, [id]);

return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}

function Author({id}) {
const [author, setAuthor] = useState('');
// NOT: Note render'ı *sonra* yüklenir.
// Pahalı bir istemci-sunucu şelalesine neden olur.
useEffect(() => {
fetch(`/api/authors/${id}`).then(data => {
setAuthor(data.author);
});
}, [id]);

return <span>By: {author.name}</span>;
}
// api
import db from './database';

app.get(`/api/notes/:id`, async (req, res) => {
const note = await db.notes.get(id);
res.send({note});
});

app.get(`/api/authors/:id`, async (req, res) => {
const author = await db.authors.get(id);
res.send({author});
});

Sunucu Bileşenleri ile veriyi okuyabilir ve bileşende render edebilirsiniz:

import db from './database';

async function Note({id}) {
// NOT: Render sırasında *yüklenir.
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}

async function Author({id}) {
// NOT: Note'dan *sonra* yüklenir,
// ancak veri aynı konumda ise hızlıdır.
const author = await db.authors.get(id);
return <span>By: {author.name}</span>;
}

Paketleyici, ardından veriyi, render edilen Sunucu Bileşenlerini ve dinamik İstemci Bileşenlerini bir pakette birleştirir. İsteğe bağlı olarak, bu paket daha sonra sunucu tarafında render edilip (SSR) sayfanın ilk HTML’ini oluşturabilir. Sayfa yüklendiğinde, tarayıcı orijinal Note ve Author bileşenlerini görmez; yalnızca render edilmiş çıktı istemciye gönderilir:

<div>
<span>Yazan: React Ekibi</span>
<p>React 19...</p>
</div>

Sunucu Bileşenleri, sunucudan tekrar alınıp veriye erişip yeniden render edilerek dinamik hale getirilebilir. Bu yeni uygulama mimarisi, sunucu odaklı Çok Sayfalı Uygulamalar’ın basit “istek/cevap” zihniyet modelini, istemci odaklı Tek Sayfa Uygulamalarının sorunsuz etkileşimiyle birleştirir ve size her iki dünyanın da en iyisini sunar.

Sunucu Bileşenlerine Etkileşim Ekleme

Sunucu Bileşenleri tarayıcıya gönderilmez, bu yüzden useState gibi etkileşimli API’leri kullanamazlar. Sunucu Bileşenlerine etkileşim eklemek için, bunları "use client" direktifini kullanarak İstemci Bileşeni ile birleştirebilirsiniz.

Not

Sunucu Bileşenleri için bir direktif yoktur.

Yaygın bir yanlış anlama, Sunucu Bileşenlerinin "use server" ile gösterildiğidir, ancak Sunucu Bileşenleri için bir direktif yoktur. Sunucu İşlevleri için "use server" direktifi kullanılır.

Daha fazla bilgi için, Direktifler dökümantasyonuna bakın.

Aşağıdaki örnekte, Notes Sunucu Bileşeni, expanded state’ini değiştirmek için state kullanan bir Expandable İstemci Bileşenini içe aktarır:

// Sunucu Bileşeni
import Expandable from './Expandable';

async function Notes() {
const notes = await db.notes.getAll();
return (
<div>
{notes.map(note => (
<Expandable key={note.id}>
<p note={note} />
</Expandable>
))}
</div>
)
}
// İstemci Bileşeni
"use client"

export default function Expandable({children}) {
const [expanded, setExpanded] = useState(false);
return (
<div>
<button
onClick={() => setExpanded(!expanded)}
>
Toggle
</button>
{expanded && children}
</div>
)
}

Bu, önce Notes’u bir Sunucu Bileşeni olarak render edip, ardından paketleyiciye Expandable İstemci Bileşeni için bir paket oluşturması talimatı vererek çalışır. Tarayıcıda, İstemci Bileşenleri, prop olarak geçirilen Sunucu Bileşenlerinin çıktısını görecektir:

<head>
<!-- İstemci Bileşenleri için paket -->
<script src="bundle.js" />
</head>
<body>
<div>
<Expandable key={1}>
<p>bu ilk nottur</p>
</Expandable>
<Expandable key={2}>
<p>bu ikinci nottur</p>
</Expandable>
<!--...-->
</div>
</body>

Sunucu Bileşenleri ile Async Bileşenleri

Sunucu Bileşenleri, async/await kullanarak Bileşen yazmanın yeni bir yolunu tanıtır. Bir async bileşen içinde await kullandığınızda, React render’lamaya devam etmeden önce promise’in çözülmesini bekler. Bu, sunucu/istemci sınırlarında, Suspense için stream desteğiyle çalışır.

Hatta sunucuda bir promise oluşturabilir ve bunu istemcide bekleyebilirsiniz:

// Sunucu Bileşeni
import db from './database';

async function Page({id}) {
// Sunucu Bileşenini askıya alır.
const note = await db.notes.get(id);

// NOT: beklenmemiş, burada başlayacak ve istemcide bekleyecek.
const commentsPromise = db.comments.get(note.id);
return (
<div>
{note}
<Suspense fallback={<p>Yorumlar Yükleniyor...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</div>
);
}
// İstemci Bileşeni
"use client";
import {use} from 'react';

function Comments({commentsPromise}) {
// NOT: Bu, sunucudan gelen promise'i yeniden başlatacak.
// Veriler mevcut olana kadar askıya alınacak.
const comments = use(commentsPromise);
return comments.map(commment => <p>{comment}</p>);
}

note içeriği, sayfanın render edilmesi için önemli bir veri olduğu için, sunucuda await edilir. Yorumlar ise daha aşağıda ve önceliği düşük olduğundan, promise’i sunucuda başlatırız ve istemcide use API’si ile bekleriz. Bu, istemcide askıya alınacak, ancak note içeriğinin render edilmesini engellemeyecektir.

Async bileşenler istemcide desteklenmediği için, promise’i use ile bekleriz.