Suspense, içindeki bileşenler henüz hazır değilken bir yükleme mesajı gibi bir yedek içerik (fallback) gösterir. React.lazy beklenecek en yaygın şeydir: bir bileşenin kodunu yalnızca o bileşen ilk render olduğunda yükler, bu da ilk indirmeyi küçük tutar.
Show chart'a tıkla: bir saniye boyunca Loading chart... görünür, sonra grafik. Gizle ve yeniden göster; yeni bir kayıt satırı olmadan hemen görünür, çünkü lazy yüklenen modülü tutar.
lazy ile kod bölme
Editör her şeyi tek bir dosyada tutar, bu yüzden örnek yavaş modülü elle oluşturur: bir saniye sonra default export'u olan bir nesneye resolve olan bir promise. Gerçek bir uygulamada bileşen kendi dosyasında yaşar ve dinamik bir import aktarırsın:
import { lazy, Suspense } from 'react';
const Chart = lazy(() => import('./Chart.jsx'));
export default function Dashboard() {
return (
<Suspense fallback={<p>Loading chart...</p>}>
<Chart />
</Suspense>
);
}
import('./Chart.jsx') modül için bir promise döndürür. Vite ve webpack gibi paketleyiciler dinamik import'u görür ve Chart.jsx ile yalnızca onun kullandığı her şeyi, <Chart /> ilk render olduğunda indirilen ayrı bir dosyaya koyar. Bilmen gereken kurallar:
- Modülün bir varsayılan export'a ihtiyacı vardır.
lazy, promise'in resolve olduğu şeyindefaultözelliğini okur. Adlandırılmış bir export için onu eşle:lazy(() => import('./charts.js').then((m) => ({ default: m.LineChart }))). lazy'yi bir modülün en üst düzeyinde çağır. Bir bileşenin içinde her render'da yeni bir bileşen tipi oluştururdu, bu yüzden React eskisini unmount eder, state'ini kaybeder ve onu yeniden yüklerdi.- Kullanıcının zaten beklediği yerde böl. Rotalar, modallar, nadiren açılan paneller ve ağır widget'lar (editörler, grafikler, haritalar) iyi adaylardır. Her küçük bileşeni bölmek hiçbir kazanç olmadan istekler ve yükleme durumları ekler.
Tıklamadan önce önceden yüklemek
Lazy bir bileşen ilk render olduğunda yüklenmeye başlar, bu yüzden kullanıcı tıkladıktan sonra her zaman en az bir indirme bekler. Tıklamanın geleceğini tahmin edebiliyorsan indirmeyi daha erken başlat. Import fonksiyonunu bir değişkende tut ve onu hover ya da focus'ta çağır; tarayıcı modülü tutar, böylece lazy aynı import'u sonra çağırdığında ikinci bir indirme olmadan resolve olur.
const loadChart = () => import('./Chart.jsx');
const Chart = lazy(loadChart);
<button onMouseEnter={loadChart} onFocus={loadChart} onClick={() => setShow(true)}>
Show chart
</button>
Suspense ne göstereceğine nasıl karar verir
<Suspense> içindeki bir bileşen hazır değilken askıya alınır (suspend): React o kısmı render etmeyi durdurur ve üstündeki en yakın Suspense'in fallback'ini gösterir. O sınırın içindeki her şey, yalnızca bekleyen bileşen değil, yedek içerikle değiştirilir. Beklediği şey hazır olduğunda React içeriği yeniden render eder ve yedek içeriği değiştirir.
Bu da sınırın konumunu bir tasarım kararı yapar. Bağımsız parçaları kendi sınırlarına koy, böylece her biri hazır olduğunda görünebilir:
Önce tüm sayfa Loading page... gösterir, çünkü Header dış sınıra aittir. Başlık hazır olduğunda makale altında Loading comments... ile görünür ve yorumlar en son gelir. 1500'ü 3000 yap; önizleme yalnızca yorumlar için daha uzun bir bekleyişle yeniden yüklenir. İçteki <Suspense>'i sil (<Comments />'i koru); sayfa hiçbir şey göstermeden önce yorumları bekler.
React 19'da use() ile Suspense
React 19'da bir bileşen use(promise) ile bir promise okuyabilir. Promise hâlâ beklemedeyse bileşen askıya alınır ve en yakın Suspense yedek içeriğini gösterir; resolve olduğunda use değeri döndürür. use hook'u sayfası onu bütünüyle anlatıyor. Aşağıdaki sahte fetchUser gerçek bir isteğin yerine geçer.
Kullanıcılar arasında tıkla, sonra User 1'e geri dön: anında görünür ve Console yeni bir fetch kaydetmez, çünkü promise zaten önbellektedir.
Promise neden önbelleğe alınmalı
cache haritası burada bir optimizasyon değil, bir zorunluluktur. Askıya alınan bir bileşen o denemeden hiçbir şey tutmaz: promise resolve olduğunda React onu baştan yeniden render eder. Profile, fetchUser(id)'yi doğrudan çağırsaydı her deneme yeni bir promise oluşturur, yeni bir istek başlatır ve onun üzerinde yeniden askıya alınırdı. Profil hiç görünmez ve Console fetching user satırlarıyla dolar. Bu yüzden promise render'dan uzun yaşayan bir yerden gelmelidir:
- yukarıdaki harita gibi isteğe göre anahtarlanmış bir önbellek (TanStack Query gibi veri kütüphaneleri ve framework loader'ları bunu senin için yapar);
- promise'i bir olay işleyicisinde ya da bir Server Component'te bir kez oluşturup prop olarak aşağı aktaran bir ebeveyn.
Her yeni kullanıcının yine de profili yedek içerikle değiştirdiğine dikkat et. Bir sonraki hazır olana kadar eski kullanıcıyı ekranda tutmayı tercih edersen güncellemeyi bir transition'a sar: startTransition(() => setId(n)). React, bir transition için zaten görünür olan içeriği gizlemez (bkz. useTransition).
Hataların bir error boundary'ye ihtiyacı var
Suspense başarısız olmayı değil, beklemeyi ele alır. Bir lazy import başarısız olursa (kullanıcı çevrimdışı oldu, yeni bir dağıtım eski parçayı kaldırdı) ya da use'a aktarılan bir promise reddedilirse, React hatayı en yakın error boundary'ye fırlatır. Bir tane olmadan kökün altındaki tüm ağaç unmount olur. Error boundary'ler hâlâ class bileşenleridir (bkz. error boundary'ler):
<ErrorBoundary fallback={<p>Could not load the chart.</p>}>
<Suspense fallback={<p>Loading chart...</p>}>
<Chart />
</Suspense>
</ErrorBoundary>
Suspense'in algılamadıkları
Suspense yalnızca askıya alınan bileşenlere tepki verir: lazy bileşenler, use(promise) ve Suspense için yapılmış veri kaynakları (framework loader'ları, Suspense destekli kütüphaneler). Tamamlandığında state ayarlayan useEffect içindeki bir fetch askıya alınmaz, bu yüzden etrafındaki bir Suspense sınırı yedek içeriğini asla göstermez. Bu kalıp için, veri çekme sayfasında gösterildiği gibi kendi loading state'ini tutarsın.
Sıkça Sorulan Sorular
React Suspense nedir?
<Suspense fallback={...}>, içindeki herhangi bir bileşen bir şey beklerken, örneğin lazy yüklenen kod ya da use ile okunan veri, fallback'ini gösteren bir bileşendir. İçerideki her şey hazır olduğunda React yedek içeriği içerikle değiştirir.
React.lazy ne yapar?
lazy(() => import('./Chart.jsx')), kodu ilk render olduğunda indirilen bir bileşen oluşturur. Paketleyiciler o dosyayı ayrı bir parçaya koyar, böylece ilk sayfa daha az JavaScript yükler.
Suspense veri çekme için çalışır mı?
Evet, veri kaynağı destekliyorsa. React 19'da bir bileşen use(promise) ile bir promise okuyabilir ve resolve olana kadar askıya alınabilir. Next.js gibi framework'ler de Suspense'i kendi veri yüklemeleriyle bütünleştirir. useEffect içindeki bir fetch Suspense'i tetiklemez.
Suspense ile hatalar nasıl ele alınır?
Suspense yalnızca beklemeyi ele alır. Bir lazy import ya da bir promise başarısız olursa hata en yakın error boundary'ye gider, bu yüzden Suspense sınırını (ya da ebeveynini) bir tanesiyle sar.
lazy'yi nerede çağırmalıyım?
Bir modülün en üst düzeyinde, herhangi bir bileşenin dışında. lazy'yi bir bileşenin içinde çağırmak her render'da yeni bir bileşen tipi oluşturur; bu da state'ini sıfırlar ve onu yeniden yükler.