Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Fallback-Rendering, Code-Splitting und Streaming-SSR mit Suspense-Grenzen.
Suspense ist ein Mechanismus, der Komponenten erlaubt, auf etwas zu 'warten', bevor sie rendern. Wenn eine Komponente nicht bereit ist (ein Lazy-Import wurde nicht geladen oder ein Datenabruf wurde nicht aufgelöst), wirft sie ein Promise. React fängt dieses geworfene Promise ab, findet die nächste Suspense-Grenze im Baum, zeigt dessen Fallback-Benutzeroberfläche und rendert den Teilbaum automatisch neu, wenn das Promise aufgelöst wurde.
React.lazy(() => import('./MeineKomponente')) hüllt einen dynamischen Import in einen speziellen Wrapper ein. Wenn React es zum ersten Mal rendern versucht, wurde der Import noch nicht aufgelöst, also wirft der Lazy-Wrapper ein Promise.
Während der Render-Phase fängt React geworfene Promises ab (nicht geworfene Fehler — die gehen zu Fehlergrenzen). Es durchläuft den Fiber-Baum nach oben, um den nächsten <Suspense>-Vorfahren zu finden, und rendert dessen fallback-Prop anstelle des ausgesetzten Teilbaums.
React hängt einen .then()-Handler an das geworfene Promise. Wenn das Promise aufgelöst wird (das Modul geladen, oder Daten eingetroffen sind), rendert React den ausgesetzten Teilbaum von Grund auf neu, diesmal mit dem aufgelösten Wert.
Im Concurrent Mode kann React 'versuchen', einen ausgesetzten Teilbaum zu rendern, ohne ihn im DOM zu committen. Wenn er aussetzt, zeigt React weiterhin den alten Inhalt (kein Flash zum Fallback), bis das Promise aufgelöst wird, dann tauscht es den neuen Inhalt atomisch ein.
Mit Next.js/React 18 Streaming sendet der Server sofort die HTML-Shell mit Suspense-Fallbacks. Wenn jeder asynchrone Datenabruf auf dem Server aufgelöst wird, streamt React den gefüllten HTML-Chunk zum Browser und ersetzt den Fallback inline.
Hüllt einen dynamischen Import ein, um eine lazy-geladene Komponente zu erstellen. Beim ersten Rendering wird ein Promise geworfen. Nach der Auflösung rendert React mit dem geladenen Modul neu.
<Suspense fallback='{'<Spinner/>'}'> fängt geworfene Promises von beliebigen Nachfahren ab. Zeigt Fallback während des Wartens, enthüllt Kinder, wenn sie bereit sind.
Der Mechanismus, mit dem Komponenten signalisieren, dass sie nicht bereit sind. Jede Komponente (nicht nur Lazy-Komponenten) kann ein Promise werfen, um Suspense auszulösen — so integrieren sich Datenabruf-Bibliotheken wie React Query.
Server sendet HTML progressiv. Shell rendert sofort mit Fallbacks. Wenn asynchrone Arbeit abgeschlossen ist, streamt React gefüllte Inhalt-Chunks zum Browser.
Koordiniert die Enthüllungsreihenfolge mehrerer Suspense-Grenzen. Kann 'together' (alle auf einmal enthüllen) oder 'forwards'/'backwards' (in Reihenfolge enthüllen) erzwingen.
1// Code splitting with React.lazy2const HeavyChart = React.lazy(() => import('./HeavyChart'));34function App() {5 return (6 <Suspense fallback={<Spinner />}>7 <HeavyChart /> {/* loads the JS bundle on demand */}8 </Suspense>9 );10}1112// Data fetching (with a Suspense-compatible library)13// The library throws a Promise internally when data isn't ready14function UserProfile({ userId }) {15 // use() hook (React 19) or library wrapper throws if not ready16 const user = use(fetchUser(userId));17 return <h1>{user.name}</h1>;18}1920<Suspense fallback={<ProfileSkeleton />}>21 <UserProfile userId={1} />22</Suspense>
Suspense vereint drei zuvor separate Probleme — Code-Splitting, Ladezustände und Server-Streaming — unter einem deklarativen Modell. Anstatt isLoading-Flags in jeder Komponente zu verfolgen, deklarieren Sie eine Grenze und einen Fallback, und React übernimmt die Orchestrierung. Dies führt zu erheblich einfacherem Datenabruf-Code und eliminiert Fehler beim Verwalten von Ladezuständen.
Your SPA ships a 2MB JavaScript bundle. The landing page only needs 200KB of it — the rest is for the admin panel, chart library, and markdown editor that 80% of users never visit.
Without code splitting, the browser downloads, parses, and executes 2MB of JavaScript before showing anything. On mobile (3G network + slower CPU), this means 8+ seconds before the page is interactive — most users bounce.
Use `React.lazy` + `Suspense` at route boundaries: `const AdminPanel = React.lazy(() => import('./AdminPanel'))`. The initial bundle drops to 200KB. When a user navigates to /admin, the AdminPanel chunk loads on demand. The Suspense fallback shows a skeleton while the chunk downloads.
Takeaway: Code splitting at route boundaries is the highest-impact optimization for initial load time. Wrap lazy-loaded routes in Suspense with skeleton fallbacks. Tools like Next.js do this automatically for page components, but you should also lazy-load heavy feature components within pages.
A dashboard page loads 3 things: sidebar navigation (fast), main content (medium), and analytics charts (slow). With a single Suspense boundary, the entire page shows a spinner until the slowest component (charts) finishes loading.
One Suspense boundary at the page level means the entire page is either 'loading' or 'ready'. The sidebar (50ms) and main content (200ms) are ready quickly, but the user stares at a full-page spinner for 2 seconds waiting for the charts.
Nest multiple Suspense boundaries: one around the sidebar (shows instantly), one around the main content (shows after 200ms), one around the charts (shows after 2s). Each section renders independently as its data/code becomes available. The page progressively reveals content instead of all-or-nothing.
Takeaway: Use nested Suspense boundaries to control loading granularity. The boundary placement determines the loading UX: coarse boundaries (page-level) show everything at once, fine boundaries (component-level) enable progressive, streaming-style loading.