Next.js 13 ile hayatımıza giren App Router ve React Server Components (RSC), frontend mimarisini kökten değiştirdi. Artık bileşenler varsayılan olarak sunucuda render edilir; JavaScript bundle'ı küçülür, veri akışı kolaylaşır ve SEO doğal olarak çalışır.
Server Components vs Client Components
Özellik
Server Component
Client Component
Render yeri
Sunucu
Tarayıcı
Bundle'a girer mi?
Hayır
Evet
useState / useEffect?
Hayır
Evet
Doğrudan DB erişimi?
Evet
Hayır
Varsayılan mı?
Evet
Hayır (gerekiyor 'use client')
// TSX //
// app/posts/page.tsx — Server Component (varsayılan)import { db } from '@/lib/db';export default async function PostsPage() { // Doğrudan veritabanı sorgusu — istemciye sızmaz const posts = await db.post.findMany({ orderBy: { createdAt: 'desc' } }); return ( <ul> {posts.map(post => <PostCard key={post.id} post={post} />)} </ul> );}
Veri Akışı: Fetch ve Cache
// TSX //
// Veri alma — React.cache ile tekil istekimport { cache } from 'react';export const getPost = cache(async (slug: string) => { const res = await fetch(`https://api.example.com/posts/${slug}`, { next: { revalidate: 3600 }, // 1 saatte bir yenile }); if (!res.ok) throw new Error('Post bulunamadı'); return res.json();});// page.tsx ve layout.tsx aynı slug ile çağırsa bile tek istek gider
Server Components, React Suspense ile birleşince Streaming SSR'ı mümkün kılar — ağır veri yükleyen bileşenler beklenirken sayfanın geri kalanı tarayıcıya akar.
// TSX //
import { Suspense } from 'react';export default function DashboardPage() { return ( <div> <h1>Dashboard</h1> {/* Hızlı yüklenen kısım hemen görünür */} <QuickStats /> {/* Ağır sorgu için skeleton göster */} <Suspense fallback={<ChartSkeleton />}> <RevenueChart /> {/* async server component */} </Suspense> <Suspense fallback={<TableSkeleton />}> <RecentOrders /> {/* async server component */} </Suspense> </div> );}
Client Island Deseni
Etkileşimli bileşenler mümkün olan en küçük birimde 'use client' almalıdır:
// TSX //
// components/LikeButton.tsx'use client';import { useState, useTransition } from 'react';import { likePost } from '@/actions/post.actions';export default function LikeButton({ postId, initialCount }: { postId: string; initialCount: number }) { const [count, setCount] = useState(initialCount); const [isPending, startTransition] = useTransition(); function handleClick() { startTransition(async () => { await likePost(postId); setCount(c => c + 1); }); } return ( <button onClick={handleClick} disabled={isPending}> ♥ {count} </button> );}// Server Component içinde kullanimport LikeButton from '@/components/LikeButton';export default async function PostPage({ params }) { const post = await getPost(params.slug); return ( <article> <h1>{post.title}</h1> {/* Sadece bu küçük bileşen client bundle'a girer */} <LikeButton postId={post.id} initialCount={post.likeCount} /> </article> );}
Server Actions
React 19 ve Next.js 15+ ile form gönderimi için ayrı API route yazmak zorunda değilsiniz:
// TSX //
// actions/post.actions.ts'use server';import { z } from 'zod';import { revalidatePath } from 'next/cache';import { db } from '@/lib/db';import { getServerSession } from '@/lib/auth';const CreatePostSchema = z.object({ title: z.string().min(5).max(200), content: z.string().min(50),});export async function createPost(formData: FormData) { const session = await getServerSession(); if (!session) throw new Error('Giriş yapınız.'); const data = CreatePostSchema.parse({ title: formData.get('title'), content: formData.get('content'), }); await db.post.create({ data: { ...data, authorId: session.user.id }, }); revalidatePath('/posts');}
Sonuç
React Server Components ve Next.js App Router, web geliştirmede paradigma kaymasını temsil eder. Sunucu tarafında veri çekimi, otomatik bundle optimizasyonu ve Streaming SSR ile kullanıcı deneyimi ve geliştirici ergonomisi aynı anda iyileşir. Bir sonraki konuda Parallel Routes ve Intercepting Routes mimarisini inceleyeceğiz.