Üretimde React Server Components: desenler ve tuzaklar

Üretimde React Server Components: savaşta sınanmış desenler, sık karşılaşılan anti-desenler ve sağlam Next.js 15 uygulamaları için hata ayıklama stratejileri.

Üretimde React Server Components desenleri ve tuzakları

React Server Components (RSC), Next.js 15'te sunucu tarafı render'ın işleyişini temelden değiştirir; ancak üretim ortamındaki kullanım, resmi dokümantasyonun her zaman ele almadığı tuzakları ortaya çıkarır. Bu makale, çalışan desenleri, kırılan desenleri ve sorunların üretime ulaşmadan önce nasıl teşhis edileceğini ele alır.

Server Components ve Client Components

Bir Server Component yalnızca sunucuda çalışır ve tarayıcıya sıfır JavaScript gönderir. Bir Client Component ("use client" ile işaretlenmiş) her iki tarafta da çalışır. Kural: Client Components'i mümkün olduğunca küçük ve ağacın mümkün olduğunca alt seviyesinde tutmak.

Sunucu-istemci sınırı: boundary desenini anlamak

En yaygın RSC tuzağı, Server ve Client Components arasındaki sınırla ilgilidir. Bir bileşen "use client" direktifini taşıdığında, içe aktarılan tüm çocukları da Client Component haline gelir, direktif olmasa bile.

ProductPage.tsx (Server Component)tsx
import { ProductDetails } from './ProductDetails'
import { AddToCartButton } from './AddToCartButton'

export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const product = await getProduct(id)

  return (
    <div>
      {/* Server Component: doğrudan DB erişimi */}
      <ProductDetails product={product} />
      {/* Client Component: izole etkileşim */}
      <AddToCartButton productId={product.id} price={product.price} />
    </div>
  )
}
AddToCartButton.tsx (Client Component)tsx
'use client'

import { useState } from 'react'

export function AddToCartButton({ productId, price }: { productId: string; price: number }) {
  const [adding, setAdding] = useState(false)

  async function handleAdd() {
    setAdding(true)
    await fetch('/api/cart', {
      method: 'POST',
      body: JSON.stringify({ productId, quantity: 1 }),
    })
    setAdding(false)
  }

  return (
    <button onClick={handleAdd} disabled={adding}>
      {adding ? 'Ekleniyor...' : `Sepete ekle — $${price}`}
    </button>
  )
}

Anahtar desen: verileri Server Component'tan Client Component'a serileştirilebilir prop'lar olarak geçirmek. Fonksiyonlar, sınıflar ve Date nesneleri bu sınırı geçemez.

Anti-desen: gereksiz Client Component sarmalayıcısı

Sık yapılan bir hata, Server Component çocuklarını saran bir Client Component oluşturmak ve böylece tüm alt ağacı istemci tarafına zorlamaktır.

PageWrapper.tsx — ANTİ-DESENtsx
'use client'

import { useState } from 'react'

// Tüm çocuk içerik istemci tarafına geçer
export function PageWrapper({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState('light')
  return (
    <div className={theme}>
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        Temayı değiştir
      </button>
      {children}
    </div>
  )
}
slot olarak children

Çözüm: Server Components'i children olarak geçirmek (slot deseni). Prop olarak geçirilen çocuklar, ebeveyn bir Client Component olsa bile Server Components olarak kalır. Yukarıdaki kod, children bir Server Component ebeveynden geldiği sürece doğru şekilde çalışır.

layout.tsx (Server Component)tsx
import { PageWrapper } from './PageWrapper'
import { HeavyServerContent } from './HeavyServerContent'

export default function Layout() {
  return (
    <PageWrapper>
      {/* İstemci sarmalayıcısına rağmen Server Component olarak kalır */}
      <HeavyServerContent />
    </PageWrapper>
  )
}

Bu kompozisyon deseni, ağır içerik için sunucu render'ının faydalarını korurken sarmalayıcı seviyesinde etkileşimi mümkün kılar.

Asenkron veri işleme: bileşende fetch deseni

React 19 ve Next.js 15, Server Components içinde doğrudan async/await desteği sunar. Bu desen, eski getServerSideProps yaklaşımına kıyasla veri çekmeyi basitleştirir.

UserProfile.tsx (Server Component)tsx
import { cache } from 'react'

// Aynı render içindeki özdeş çağrıları tekilleştirir
const getUser = cache(async (userId: string) => {
  const res = await fetch(`https://api.example.com/users/${userId}`, {
    next: { revalidate: 3600 }, // 1 saat cache
  })
  if (!res.ok) throw new Error('User not found')
  return res.json()
})

export default async function UserProfile({ userId }: { userId: string }) {
  const user = await getUser(userId)

  return (
    <section>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <p>Üyelik tarihi: {new Date(user.createdAt).toLocaleDateString('tr-TR')}</p>
    </section>
  )
}

Üç kritik nokta:

  • React'in cache() fonksiyonu, tek bir sunucu render'ı sırasında özdeş çağrıları tekilleştirir
  • next: { revalidate }, Next.js tarafında cache süresini kontrol eder
  • Asenkron bir Server Component'taki hatalar en yakın error.tsx'i tetikler

Serileştirme tuzağı: sınırı geçemeyenler

Server ve Client Components arasında değişen verilerin JSON ile serileştirilebilir olması gerekir. Sessiz hatalara veya çökmelere yol açan durumlar şunlardır.

tsx
// TUZAK: serileştirilemez tipleri geçirmek
// Fonksiyon — çalışmaz
<ClientComp onSubmit={async (data) => { /* server action */ }} />
// Bunun yerine içe aktarılmış bir Server Action kullanmak
import { submitForm } from '@/lib/actions/form'
<ClientComp onSubmit={submitForm} />

// Date nesnesi — çalışmaz
<ClientComp createdAt={new Date()} />
// ISO string — çalışır
<ClientComp createdAt={new Date().toISOString()} />

// Map, Set, RegExp — çalışmaz
<ClientComp data={new Map([['key', 'value']])} />
// Düz nesne veya dizi — çalışır
<ClientComp data={{ key: 'value' }} />

Server Actions ("use server" ile işaretli fonksiyonlar) istisnadır: Next.js bunları HTTP endpoint'lerine dönüştürdüğü için bir Client Component'a prop olarak geçirilebilirler.

React / Next.js mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Streaming ve Suspense: ilerlemeli yükleme desenleri

Suspense ile SSR streaming, HTML'i tarayıcıya ilerlemeli olarak gönderir. Optimal desen, her asenkron bölümün etrafında granüler Suspense sınırları kullanır.

DashboardPage.tsx (Server Component)tsx
import { Suspense } from 'react'
import { RevenueChart } from './RevenueChart'
import { RecentOrders } from './RecentOrders'
import { UserStats } from './UserStats'

export default function DashboardPage() {
  return (
    <div className="grid grid-cols-2 gap-6">
      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart />
      </Suspense>

      <Suspense fallback={<StatsSkeleton />}>
        <UserStats />
      </Suspense>

      <Suspense fallback={<TableSkeleton />}>
        <RecentOrders />
      </Suspense>
    </div>
  )
}

Her bölüm bağımsız olarak yüklenir. RevenueChart 3 saniye, UserStats 200 ms sürerse, istatistikler grafiği beklemeden anında görünür.

Suspense ve SEO

Bir Suspense sınırı içindeki içerik sunucuda render edilir ve ilk HTML'e dahil edilir. Crawler'lar tam içeriği görür. Streaming yalnızca tarayıcıya teslim hızını etkiler, SEO görünürlüğünü değil.

Üretimde hata ayıklama: RSC sorunlarını izleme

RSC hataları çoğu zaman karmaşıktır. Üç teşhis tekniği üretimde işe yarar.

1. Hidrasyon uyumsuzluklarını tespit etmek

debug-hydration.tsxtsx
'use client'

import { useEffect, useState } from 'react'

export function HydrationDebug() {
  const [isClient, setIsClient] = useState(false)

  useEffect(() => {
    setIsClient(true)
  }, [])

  if (process.env.NODE_ENV !== 'development') return null

  return (
    <div style={{ position: 'fixed', bottom: 0, right: 0, padding: '4px 8px', fontSize: 12 }}>
      {isClient ? 'Client' : 'Server'}
    </div>
  )
}

2. RSC payload'ını loglamak

Next.js 15'te next.config.ts içinde RSC log'larını etkinleştirmek:

next.config.tstypescript
const nextConfig = {
  logging: {
    fetches: {
      fullUrl: true, // Tam fetch URL'lerini gösterir
    },
  },
}

export default nextConfig

3. Payload boyutunu kontrol etmek

Aşırı büyük bir RSC payload'ı (> 128 KB) performansı düşürür. DevTools'ta text/x-component content type'lı ağ isteklerini izlemek gerekir.

İleri desen: Server Actions ile kompozisyon

Server Actions ve Server Components birleşimi doğal bir CQRS deseni oluşturur: sunucuda okumalar (RSC), action'lar üzerinden yazmalar.

TodoList.tsx (Server Component)tsx
import { getTodos } from '@/lib/services/todo'
import { TodoForm } from './TodoForm'
import { deleteTodo } from '@/lib/actions/todo'

export default async function TodoList() {
  const todos = await getTodos()

  return (
    <div>
      <TodoForm />
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            {todo.title}
            <form action={deleteTodo}>
              <input type="hidden" name="id" value={todo.id} />
              <button type="submit">Sil</button>
            </form>
          </li>
        ))}
      </ul>
    </div>
  )
}
actions/todo.tstsx
'use server'

import { revalidatePath } from 'next/cache'
import { TodoService } from '@/lib/services/todo'

export async function deleteTodo(formData: FormData) {
  const id = formData.get('id') as string
  await TodoService.delete(id)
  revalidatePath('/todos')
}

revalidatePath çağrısı, tam sayfa yenilemesi olmadan güncellenmiş verilerle yeni bir Server Component render'ını tetikler.

Bu konularda daha derin mülakat hazırlığı için SharpSkill'deki Next.js Server Actions modülü ile Next.js Data Fetching modülünü incelemek faydalıdır. Resmi React dokümantasyonu Server Components spesifikasyonunun tamamını ele alır.

Sonuç

  • Client Components'i bileşen ağacının altında küçük ve izole tutmak
  • Bir istemci sarmalayıcısı içinde Server Components'i korumak için slot desenini (children) kullanmak
  • Sunucu-istemci sınırını geçen prop'ların serileştirilebilirliğini her zaman doğrulamak
  • Her bağımsız asenkron bölümün etrafına granüler Suspense sınırları yerleştirmek
  • Üretimde RSC payload boyutunu izlemek (hedef < 128 KB)
  • Doğal bir CQRS deseni için Server Components (okumalar) ve Server Actions (yazmalar) birleşimini kullanmak
  • Tek bir sunucu render'ı içindeki istekleri tekilleştirmek için React'in cache() fonksiyonunu kullanmak

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Etiketler

#react server components
#next.js 15
#rsc patterns
#production
#react 19

Paylaş

İlgili makaleler