React Render Performans Optimizasyonu
React Suspense, Lazy Loading ve RSC (React Server Components) konseptleri ile gereksiz re-render pro...
Bu makale Frontend alanındaki deneyimlerimi ve yazılım geliştirme metodolojimi aktarmaktadır.
Genel Bakış
React Suspense, Lazy Loading ve RSC (React Server Components) konseptleri ile gereksiz re-render problemlerinin nasıl önüne geçilir? UseMemo ve UseCallback hook'larının doğru zamanda kullanımı üzerine case studyler inceledik.
React'in popülerliği arttıkça, sıkça karşılaşılan performans darboğazlarının başında "gereksiz tekrar render edilme" (unnecessary re-renders) sorunu geliyor. Dashboardlarında 2000'den fazla DOM node'u barındıran Elly admin panelimizde bu problemi bizzat yaşadık: bir filtreleme input'una her karakter yazıldığında tüm tablo satırları yeniden render ediliyordu ve Chrome DevTools Profiler'da 300ms+ render süreleri görüyorduk.
Çözüm sürecinde React DevTools Profiler ile render waterfall'ları analiz ettik ve darboğaz noktalarını tespit ettik. React.memo, useMemo ve useCallback fonksiyonlarını her yerde değil, yalnızca profiler'da kırmızı işaretli olan bileşenlerde uyguladık. Premature optimization tuzağına düşmeden, veri odaklı karar aldık.
// ❌ Anti-pattern: Her render'da filtre fonksiyonu yeniden oluşur
function Dashboard({ items }) {
const filtered = items.filter(i => i.active) // Her render'da çalışır
return <DataTable data={filtered} />
}
// ✅ Doğru yaklaşım: Sadece items değiştiğinde hesapla
function Dashboard({ items }) {
const filtered = useMemo(() => items.filter(i => i.active), [items])
return <DataTable data={filtered} />
}
// Ağır liste bileşenlerini memo ile sarmalama
const TableRow = memo(function TableRow({ row }: { row: RowData }) {
return (
<tr>
<td>{row.name}</td>
<td>{row.status}</td>
</tr>
)
})10.000+ satırlık tablolar için ise @tanstack/react-virtual (eski adıyla react-window) ile virtualization uyguladık. Ekranda yalnızca görünen 20-30 satır DOM'a yazılıyor, geri kalanı viewport dışında kalıyor. Bu teknikle initial render süremiz 300ms'den 18ms'ye düştü.
React Server Components (RSC) ile bu optimizasyonların bir kısmı artık sunucu tarafında çözülüyor: veri çekme ve filtreleme sunucuda yapılıp istemciye sadece render-ready HTML gönderiliyor. Bu da JavaScript bundle boyutunu küçültürken, Time to Interactive (TTI) metriğini dramatik biçimde iyileştiriyor. Sonuç olarak Lighthouse Performance skoru 62'den 94'e yükseldi.
useTransition ve useDeferredValue
Ağır filtrelemede kullanıcı girişini düşük öncelikli güncellemeye itmek için startTransition veya useDeferredValue, yazdıkça düşen kare hızını toparlar; bazen yoğun useMemo kullanımından daha sade bir çözümdür.
Profiler Önceliği ve Aşırı Memo
Her bileşeni memo ile sarmak okunabilirliği düşürür. Küçük listelerde virtualization şart değildir — ölçmeden optimizasyon yapılmamalıdır.
Bundle ve Sunucu Tarafı İş
Veri işini sunucuya itmek JS indirimini ve hydrate maliyetini azaltır; istemci sınırında yalnızca etkileşim ağacı bırakmak Long Task riskini düşürür.
Liste ve Tablolarda Anahtar Disiplini
Uzun süre yaşayan listelerde key olarak indeks kullanmak sıralama, filtreleme veya sayfalama sonrasında React reconciliation’ında gereksiz DOM güncellemelerini artırır. Stabil kimlik anahtarı (örneğin veri kaydının doğal birincil anahtarı) seçildiğinde aynı satır bileşeni gerçekten aynı ürünle eşlenir ve odak ile iç durum daha az dağıtılır. Tablolarda seçili satır, inline düzen veya accordion gibi lokal durumlar varsa bu ayrım pratikte anlaşılmaz mikro kopuklukları bile engeller.
Context Zincirini Dar Tutma
Birden fazla context katmanında yüksek frekanslı state tutmak, gereksiz re-render yayılımına neden olur. Context bölmeleri gerektiğinde ayrılır ya da sık güncellenen değerler alt ağaca prop veya daha hedeflenmiş store katmanına taşınır. Global store’a her UI detayının konması çoğu kurumsal uygulamada yanlış ölçeklenmiş bir soyutluktur; server state için veri katmanı, istemci arayüz durumu için daha küçük kapsayıcılar daha iyi uyum sunar.
Kod Bölümleme ve Ağır Bileşenler
Grafik kütüphaneleri, zengin metin düzenleyiciler ve harita bileşenleri ilk pakete gömülürse bağlantı gecikmesi ve hydrate maliyeti büyür. React.lazy ile rota bazlı veya özellik bazlı dinamik import, kullanıcı akışına göre gerektiğinde yüklenmesini sağlar. Suspense sınırına anlamlı bir fallback vermek, algılanan performansa doğrudan etki ettiği için loading UX’ini ihmal etmek doğru ekonomik seçim olmaktan çıkar.
Hydration Tutarlılığı
SSR veya RSC sonrasında istemci kökünde üretilen ağacın ilk render’ının sunucu çıktısıyla örtüşmemesi uyarı döngüleri ve çift iş yükü oluşturur. Etkileşim gerektiren alanların istemci sınırına alınması, tarayıcıya özgü API erişiminin sunucuya sızmaması ve suppressHydrationWarning gibi çıkışların yalnızca haklı kenar durumlarda kullanılması, prodüksiyonda sessiz uyarıları azaltır.
Interaction to Next Paint (INP) ve Ana İş Parçacığı
Ağır tıklama veya klavye handler zincirleri INP metriğini düşürür; uzun senkron hesap blokları küçük parçalara ya da zaman dilimlerine (örneğin requestIdleCallback veya zamanlanmış güncelleme modellerine) bölmek gerekebilir. flushSync yalnızca bilinçli ve seyrek senaryolarda kullanılmalıdır; sıradan liste filtreleri için gereksiz kuvvet güncelleme daha çok titreşim hissine yol açar.
Ölç → Hipotez → Tek Değişiklik Ritüeli
Profiler veya Lighthouse üzerinden bir hipoteze karar verdikten sonra tek optimizasyon uygulanır ve yeniden ölçüm yapılır. Aksi akışta aynı anda birden çok küçük değişiklik yapıldığında hangisinin yararlı olduğu kaybolur; memo, useCallback ve useMemo ormanına dönüşen kod tabanı okunamazlığı artırır. Performans kültürü, “sayıların konuştuğu” küçük ama sık doğrulanan iyileştirmelerden oluşmalıdır.
Suspense ve Ağ Kaynakları
Veri yüklemede uygun granular Suspense sınırları, kullanıcıya sayfanın ne kadarının hazır olduğunu adım adım göstererek algılanan hızı artırır. Tam sayfa bloklayan tek yükleme animasyonundan kaçınıp kritik içeriği önceliklendiren bir model tercih edilir; gereksiz istek birleştirme ve paralel prefetch ile su birikintisi azaltılır.
Form ve Controlled Input Yoğunluğu
Filtreleme kutularında her tuşta tüm liste ağacını sıçratmak klasik tuzaktır; gerekirse değeri erteleyerek veya startTransition ile düşük önceliklendirerek ana iş parçacığında gereksiz baskı oluşması engellenir. Field sayısı yüksek formlarda form kütüphanelerinin gereksiz re-render çıkarmaması için alan bileşen izolasyonu gözden geçirilir.
Geliştirici Deneyimi ile Ölçüm Araçları
React DevTools Profiler’ı prod benzeri dataset ile çalıştırmak bazen unutulur; staging’de anonymize edilmiş büyük örnek JSON ile test ortamı oluşturmak, sahada görülen titreşimi erken yakalar. Büyük tabloda “anlık sıralama” özelliği açılırsa önce maliyetinin kabul edildiğini ürün ile hizalamak gerekir — yalnızca mühendislik değil kullanılabilirlik-risk dengelemesidir.
Güvenli Varsayılanlar ve Prop Sözleşmeleri
Performansla çelişmeyen ergonomi hedeflenir: buton ve form kontrollerinde gereksiz yeniden bağlama olmayan varsayılan davranışlar ve net prop sözleşmeleri hem hızı hem bakımı güvence altına alır. Küçük bileşenlerde erken memo yerine ölçümle doğrulanmış uygulama, büyük bileşenlerde ise state’i yukarı sızdırmadan parçalama tercih sırasını belirler.
Sonuç Olarak
React performansı tek bir sihirli hook değildir; ölçüm, kullanıcı senaryosu ve mimari sınırların birlikte düşünülmesiyle uzun vadede hem hız hem sürdürülebilirlik elde edilir.
Virtualization, server tarafı iş ve etkileşim erteleme gibi teknikler aynı anda değil öncelik sırasıyla devreye alındığında regresyon riski azalır ve ekip aynı dilde konuşmaya devam eder.
Üretim ortamında bile geçici olarak “render sayacı” veya basit metrikler ekleyip anomali gözlemek, ölçüm kültürünü canlı tutmanın pratik yoludur.
Kısa süreli optimizasyon sprintleri, özellikle veri yoğun ekranlarda backlog’dan sıyrılıp geri dönüşü ölçülebilir hale getirir.
Bu içerik kişisel geliştirme laboratuvarımdan ve prodüksiyon maceralarımdan derlenmiştir.