Bu makalede7 bölüm
- 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
childrenveya 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ı,
Dategeç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-onlypaketini 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ı birProviders.tsxClient 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
useEffectile ç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/dynamickullanı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.
- 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ığı
Modern Web Uygulamaları