ERETEK
BİLGİ SİSTEMLERİ

Next.js App Router: Server ve Client Component Sınırı Nasıl Çizilir

Next.js App Router’da 'use client' sınırı, veri sızdıran prop’lar, önbellek niyeti, Server Actions yetki kontrolü ve paket boyutu ölçümü için ekip standardı.

Bu makalede7 bölüm
Teknik özet
Teknoloji
Next.js App Router, React Server Components, Server Actions, TypeScript
Konu
Sunucu/istemci bileşen sınırı ve veri akışı
Seviye
Orta–ileri
Ön koşullar
  • React bileşen ve hook bilgisi
  • Temel Next.js proje yapısı
  • TypeScript okuryazarlığı

Next.js App Router projelerinde kod incelemesine gelen sorunların büyük kısmı aynı yerden çıkar: 'use client' sınırı yanlış yere konmuştur. Uygulama çalışır, ekranlar açılır; ama istemciye giden JavaScript şişer, veri tabanı sorgusu tarayıcıya taşınır ve bir müşterinin iç notları ağ trafiğinde görünür hale gelir. Bu rehber, kurumsal iş uygulaması geliştiren ekipler için Server ve Client Component ayrımını, önbellek niyetini ve Server Actions güvenliğini tek bir ekip standardına bağlar.

App Router’ın getirdiği model

Pages Router’da sayfa bir bütün olarak sunucuda render edilir, tarayıcıda hidrasyona uğrardı; veri getServerSideProps veya getStaticProps gibi sayfa düzeyi fonksiyonlarla gelirdi. App Router (app/ dizini) bu modeli bileşen düzeyine indirir: app/ altındaki her bileşen varsayılan olarak React Server Component’tir. Bileşen asenkron olabilir, doğrudan veri kaynağına gidebilir ve çıktısı tarayıcıya JavaScript olarak değil, serileştirilmiş bir render sonucu (RSC payload) olarak gider.

Dosya kuralları bu modelin parçasıdır: layout.tsx kalıcı kabuğu, page.tsx rotanın içeriğini, loading.tsx Suspense yedeğini, error.tsx hata sınırını, route.ts HTTP uç noktasını tanımlar. error.tsx ise Client Component olmak zorundadır. Sınır, daha ilk dosyada karşınıza çıkar. Render stratejilerinin arama motoru görünürlüğüne etkisi ayrı bir konu; onu React, SSR, SSG ve teknik SEO yazısında anlattık. Burada odak bileşen mimarisi.

Server Component ve Client Component farkı

İki bileşen türü aynı JSX sözdizimini kullanır, yetenekleri ise neredeyse birbirinin tersidir. Karar verirken bu tablo yeterli bir başlangıçtır:

İhtiyaç Server Component Client Component
Veri tabanı, iç API, dosya sistemi erişimi Uygun Uygun değil
Gizli anahtar, token, bağlantı dizesi Uygun Asla
useState, useEffect, olay işleyicileri Kullanılamaz Uygun
window, localStorage, tarayıcı API’leri Kullanılamaz Uygun
Büyük biçimlendirme/markdown kütüphanesi Tercih edilir (pakete girmez) Paket boyutunu artırır
Context sağlayıcı (tema, sorgu istemcisi) Doğrudan olmaz Gerekir

Sahada en sık karıştırılan nokta şu: Client Component da ilk istekte sunucuda HTML’e render edilir, sonra tarayıcıda hidrasyona uğrar. “Client” etiketi “yalnız tarayıcıda çalışır” demek değildir; “kodu tarayıcıya da gönderilir” demektir. Bu yüzden Client Component içinde window veya localStorage erişimi useEffect içinde yapılmalı. Yapılmazsa sunucu render’ında hata ya da hidrasyon uyuşmazlığı alırsınız.

‘use client’ sınırı nereye konmalı

'use client' yönergesi bir bileşeni değil, bir modül sınırını işaretler. Yönergenin bulunduğu dosya ve bu dosyanın import ettiği her modül istemci paketine girer. Kural basit: yönergeyi ağacın yaprağına olabildiğince yakın koyun.

Kurumsal web uygulamalarında tipik bir sipariş listesi ekranı için önerdiğimiz yapı:

app/
  (panel)/
    siparisler/
      page.tsx            // Server: veriyi çeker, tabloyu render eder
      loading.tsx         // Server: iskelet görünüm
      actions.ts          // 'use server': mutasyonlar
      _components/
        SiparisTablosu.tsx   // Server: satırları üretir
        DurumFiltresi.tsx    // 'use client': select + router
        OnayButonu.tsx       // 'use client': useActionState
lib/
  db.ts                   // import 'server-only'
  siparis-servisi.ts      // import 'server-only'

Sınır hatalarının çoğu şu üç kuralla önlenir:

  • Server Component, Client Component’i import edebilir; tersi yapılamaz. Client Component içinde sunucu bileşenine ihtiyaç varsa onu children veya başka bir prop olarak dışarıdan geçirin. Bu “delik açma” deseniyle istemci tarafı bir açılır panelin içeriği sunucuda render edilmiş olarak kalır.
  • Sınırdan geçen prop’lar serileştirilebilir olmalıdır. Düz nesne, dizi, string, sayı, Date geçer; sınıf örnekleri ve sıradan fonksiyonlar geçmez. Fonksiyon geçirmeniz gerekiyorsa o bir Server Action olmalı.
  • Sunucuya ait modülleri kilitleyin. server-only paketini import eden bir modül yanlışlıkla bir Client Component zincirine girerse derleme hata verir. Veri erişim katmanındaki her dosyanın ilk satırı bu olmalı.
// lib/siparis-servisi.ts
import 'server-only';
import { db } from './db';

export async function siparisleriGetir(firmaId: string) {
  return db.siparis.findMany({
    where: { firmaId },
    select: { id: true, no: true, durum: true, tutar: true },
  });
}

Örnekteki select tesadüf değil. Server Component’ten Client Component’e geçen her prop RSC payload’ı içinde tarayıcıya ulaşır. Kullanıcı kaydının tamamını bir istemci bileşenine verirseniz, ekranda gösterilmese bile parola özeti, iç notlar ya da maliyet alanları ağ sekmesinde okunur. Ekranda görünmemek gizli olmak değildir. Ortam değişkenlerinde de aynı mantık geçerli: yalnız NEXT_PUBLIC_ önekli değişkenler istemci paketine gömülür; bu öneki gizli değerlere asla vermeyin.

Veri çekme ve önbellekleme davranışı

App Router’da veri, onu kullanan Server Component içinde await ile çekilir. Aynı render içinde aynı veriye birden fazla bileşen ihtiyaç duyuyorsa, React’in cache fonksiyonuyla sarılmış bir veri fonksiyonu istek başına mükerrer sorguyu keser. Birbirinden bağımsız çağrıları Promise.all ile paralel başlatın; ardışık await zinciri şelale gecikmesi üretir ve yavaş sayfaların klasik sebebidir.

Önbellekleme, App Router’ın sürümler arasında en çok değişen tarafıdır. Next.js 14’te fetch çağrıları varsayılan olarak önbelleğe alınıyordu; Next.js 15 ile varsayılan önbelleğe almamak oldu; daha yeni sürümlerde isteğe bağlı bir önbellek bileşeni modeli ve 'use cache' yönergesi geldi. Bu kadar oynak bir varsayılana güvenilmez. Ekip standardı olarak niyeti her çağrıda açıkça yazın:

Niyet App Router karşılığı
Her istekte taze veri fetch(url, { cache: 'no-store' })
Belirli aralıkla yenilenen veri fetch(url, { next: { revalidate: 300 } })
Olay tetikli yenileme next: { tags: ['urunler'] } + revalidateTag('urunler') (Next.js 16’da ikinci argüman olarak bir önbellek profili beklenir, örn. 'max')
Rota düzeyinde dinamik render export const dynamic = 'force-dynamic'
Yol bazlı yenileme revalidatePath('/siparisler')

Kurumsal iş uygulamalarında asıl risk, kullanıcıya veya firmaya özgü verinin paylaşılan önbelleğe düşmesidir. cookies() veya headers() okuyan bir rota dinamik render edilir; fakat ORM ile çekilen veriyi unstable_cache ya da benzeri bir mekanizmayla sararken önbellek anahtarına kiracı/firma kimliğini koymayı unutursanız, bir firmanın verisi başka bir firmaya gösterilebilir. Bu, performans hatası değil veri sızıntısıdır. Yetkiye bağlı veriyi önbelleğe alan her değişiklikte, kod incelemesinde anahtarın kimlik bilgisini içerdiğini ayrıca kontrol edin. Next.js 15 ve sonrasında cookies(), headers() ve sayfa params değerleri asenkron olduğu için await ile okunmalıdır.

Server Actions ile form işlemleri

Server Action, 'use server' yönergesiyle işaretlenmiş ve istemciden çağrılabilen asenkron bir sunucu fonksiyonudur. Bir <form> elemanının action prop’una verildiğinde JavaScript yüklenmeden de çalışır (progresif iyileştirme) ve çoğu iç form için ayrı bir API rotası yazma ihtiyacını kaldırır.

// app/(panel)/siparisler/actions.ts
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
import { oturumuGetir } from '@/lib/auth';
import { siparisiOnayla } from '@/lib/siparis-servisi';

const Girdi = z.object({ siparisId: z.string().uuid() });

export async function onayla(_onceki: unknown, formData: FormData) {
  const oturum = await oturumuGetir();
  if (!oturum || !oturum.roller.includes('Onayci')) {
    return { hata: 'Bu işlem için yetkiniz yok.' };
  }
  const sonuc = Girdi.safeParse({ siparisId: formData.get('siparisId') });
  if (!sonuc.success) return { hata: 'Geçersiz istek.' };

  await siparisiOnayla(sonuc.data.siparisId, oturum.kullaniciId);
  revalidatePath('/siparisler');
  return { hata: null };
}

İstemci tarafında React 19’un useActionState kancası eylemin dönüş değerini ve bekleme durumunu yönetir; react-dom’daki useFormStatus ise form içindeki düğmeyi gönderim sırasında devre dışı bırakır. Eski örneklerde gördüğünüz useFormState, yerini useActionState’e bıraktı.

Server Action’ların en çok atlanan özelliği şudur: derleme sırasında her eylem için bir kimlik üretilir ve eylem bu kimlikle POST isteği kabul eden bir uç nokta gibi davranır. Yani dışarıya açıktır. Düğmeyi yalnız yetkili kullanıcıya göstermek güvenlik kontrolü değildir; birisi isteği elle de gönderebilir. Yetki ve girdi doğrulaması, örnekteki gibi eylemin içinde yapılır. İstemci ve sunucuda aynı şemayla doğrulama kurmayı web formlarında doğrulama yazısı ayrıntılı anlatıyor.

Sık yapılan hatalar ve paket boyutu kontrolü

Kod incelemelerinde tekrar tekrar karşımıza çıkan hatalar ve düzeltmeleri:

  • Kök layout’a 'use client' eklemek: Genellikle bir tema veya sorgu sağlayıcısı için yapılır ve bütün uygulamayı istemci bileşenine çevirir. Doğrusu: sağlayıcıyı ayrı bir Providers.tsx Client Component’ine alın, layout’ta {children}’ı onun içine sarın. Children yine sunucuda render edilir.
  • Tarih ve yerel ayar uyuşmazlığı: Sunucunun saat dilimiyle tarayıcınınki farklıysa new Date().toLocaleString() hidrasyon uyarısı üretir. Tarihleri sunucuda sabit bir saat dilimiyle (Intl.DateTimeFormat + timeZone: 'Europe/Istanbul') biçimlendirin.
  • Veri çekmeyi gereksiz yere istemciye taşımak: İlk görünüm verisini useEffect ile çekmek boş ekran, yükleme göstergesi ve fazladan bir gidiş-dönüş demektir. İlk veri sunucudan gelsin; istemci tarafı veri kütüphaneleri sonradan güncellenen, etkileşime bağlı veriler için kalsın. Bu ayrımı React’ta durum yönetimi yazısı ele alıyor.
  • Ağır kütüphaneyi istemcide import etmek: Grafik, zengin metin editörü veya PDF görüntüleyici gibi bileşenleri yalnız gerektiği anda yüklemek için next/dynamic kullanın.
  • Tek yavaş sorgu yüzünden bütün sayfayı bekletmek: Yavaş bölümü <Suspense> ile sarın; sayfanın geri kalanı akış (streaming) ile hemen gelir.

Ölçmediğiniz paketi kontrol edemezsiniz. next build çıktısındaki boyut bilgisi sürümden sürüme değiştiği için (yeni sürümlerde rota başına boyut sütunları kaldırıldı) ölçümü bir analiz aracına bağlayın. Webpack ile derlenen projelerde @next/bundle-analyzer eklentisi iş görür; Turbopack ile derleyen sürümlerde, kullandığınız sürümün dokümantasyonundaki paket analiz seçeneğine bakın:

npm i -D @next/bundle-analyzer

// next.config.mjs
import bundleAnalyzer from '@next/bundle-analyzer';
const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === 'true' });
export default withBundleAnalyzer({});

// çalıştırma
ANALYZE=true npm run build

Sınırın zamanla bozulmaması için ekip standardına şu kontrolleri yazın:

  • Veri erişim ve gizli yapılandırma modülleri import 'server-only' ile başlıyor.
  • 'use client' içeren her dosya için kod incelemesinde “neden istemci?” sorusu yanıtlanıyor.
  • Client Component’lere tam veri modeli değil, ekranın ihtiyaç duyduğu alanlar aktarılıyor.
  • Her Server Action kendi içinde oturum, yetki ve girdi doğrulaması yapıyor.
  • Önbellek niyeti (taze, süreli, etiketli) her veri çağrısında açıkça yazılı.
  • Paket analiz raporundaki rota başına JavaScript boyutu sürümler arasında karşılaştırılıyor.

App Router’ın projeniz için doğru araç olup olmadığı henüz netleşmediyse önce WordPress mi Next.js mi karşılaştırmasına bakın. Mevcut bir uygulamayı bu mimariye taşırken veya yeni bir iç sistem tasarlarken bileşen sınırlarını ve önbellek stratejisini React ve Next.js web uygulamaları hizmetimiz kapsamında birlikte planlayabiliriz. API ayrıntısı için resmi Next.js App Router dokümantasyonu ve React’in Server Components başvuru sayfası esas alınmalı.

Sık sorulan sorular

Bir sayfanın tamamını ‘use client’ ile işaretlemek neden sorun olur?

Yönerge, dosyanın ve import ettiği tüm modüllerin istemci paketine girmesine yol açar. Veri çekme işi tarayıcıya kayar, sunucuda kalabilecek kütüphaneler indirilir ve gizli yapılandırmanın istemci koduna sızma riski artar. Etkileşimli parçayı küçük ayrı bir bileşene ayırın, yalnız onu işaretleyin.

Server Component içinde useState veya useEffect kullanılabilir mi?

Hayır. Server Component yalnız sunucuda (istek anında veya derleme zamanında) render edilir, kodu tarayıcıda hiç çalışmaz. Durum, efekt ve tarayıcı API’leri gerektiren her şey Client Component’e aittir. Server Component veriyi çeker, sonucu prop olarak etkileşimli bir Client Component’e verir.

Server Action bir API uç noktası yerine geçer mi?

Uygulamanın kendi formları ve mutasyonları için çoğu zaman evet. Mobil istemci, entegrasyon veya üçüncü taraf tüketiciler içinse sürümlenebilir bir HTTP API’si (Route Handler ya da ayrı bir servis) yine gerekir. Server Action’lar dışarıya açık POST uç noktaları gibi davrandığı için girdi doğrulaması ve yetki kontrolü her eylemin içinde ayrıca yapılmalıdır.

Önbellek davranışı neden sürümden sürüme farklı görünüyor?

Next.js, App Router’ın varsayılan önbellekleme davranışını büyük sürümlerde değiştirdi; örneğin Next.js 15 ile fetch istekleri varsayılan olarak önbelleğe alınmaz oldu, sonraki sürümlerde isteğe bağlı yeni önbellek modelleri eklendi. Projenizdeki sürümün dokümantasyonunu esas alın ve önbellek niyetini her veri çağrısında açıkça yazın.

Teknik özet
Teknoloji
Next.js App Router, React Server Components, Server Actions, TypeScript
Konu
Sunucu/istemci bileşen sınırı ve veri akışı
Seviye
Orta–ileri
Ön koşullar
  • React bileşen ve hook bilgisi
  • Temel Next.js proje yapısı
  • TypeScript okuryazarlığı
Eretek uzman desteği

Bu konu sizin ortamınızda nasıl çalışır, birlikte bakalım.

Mevcut yapınızı inceliyor, darboğazı tespit ediyor ve uygulanabilir bir iş planı öneriyoruz.