# Streaming UI Patterns: Progressive Loading für moderne Web-Apps **Meta-Description:** Design Patterns für Streaming UI mit React Suspense. Skeleton Loading, Progressive Enhancement und optimale User Experience bei langsamen Daten. **Keywords:** Streaming UI, Suspense, Skeleton Loading, Progressive Loading, React Streaming, SSR Streaming, Loading States --- ## Einführung Streaming UI bedeutet: **Zeige sofort was du hast, streame den Rest nach**. Statt einer leeren Seite oder einem Spinner sieht der User sofort Inhalte – während langsame Daten im Hintergrund laden. --- ## Das Streaming-Prinzip ``` ┌─────────────────────────────────────────────────────────────┐ │ STREAMING VS. BLOCKING │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Blocking (Traditionell): │ │ ┌────────────────────────────────────────────────────┐ │ │ │ [Spinner 3s] → [Komplette Seite] │ │ │ └────────────────────────────────────────────────────┘ │ │ User wartet 3 Sekunden auf irgendetwas │ │ │ │ Streaming (Modern): │ │ ┌────────────────────────────────────────────────────┐ │ │ │ [Header] ─ sofort │ │ │ │ [Nav] ─ sofort │ │ │ │ [Hero] ─ 100ms │ │ │ │ [Stats] ─ [Skeleton] → [Daten] ─ 500ms │ │ │ │ [Chart] ─ [Skeleton] → [Daten] ─ 1s │ │ │ │ [Table] ─ [Skeleton] → [Daten] ─ 2s │ │ │ │ [Footer] ─ sofort │ │ │ └────────────────────────────────────────────────────┘ │ │ User sieht sofort Inhalte, Details laden progressiv │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Pattern 1: Suspense Boundaries ### Strategische Platzierung ```tsx // app/dashboard/page.tsx import { Suspense } from 'react'; export default function DashboardPage() { return (
{/* Sofort gerendert - keine Suspense */}
{/* Schnelle Daten */} }>
{/* Mittlere Latenz - unabhängig voneinander */} }> }>
{/* Langsame Daten - lädt zuletzt */} }> {/* Statisch */}
); } ``` ### Nested Suspense für feinere Kontrolle ```tsx function ProductSection() { return (
}> {/* Nested: Reviews laden nach Produkten */} }>
); } ``` --- ## Pattern 2: Skeleton Components ### Anatomy-preserving Skeletons ```tsx // components/skeletons/ProductCardSkeleton.tsx export function ProductCardSkeleton() { return (
{/* Bild */}
{/* Titel */}
{/* Beschreibung */}
{/* Preis + Button */}
); } // Grid Skeleton export function ProductGridSkeleton({ count = 6 }) { return (
{Array.from({ length: count }).map((_, i) => ( ))}
); } ``` ### Shimmer Effect ```css /* styles/skeleton.css */ .skeleton-shimmer { background: linear-gradient( 90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75% ); background-size: 200% 100%; animation: shimmer 1.5s infinite; } @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } ``` ```tsx // Skeleton mit Shimmer export function ShimmerSkeleton({ className }: { className: string }) { return
; } ``` --- ## Pattern 3: Optimistic Updates ### Sofortige UI-Reaktion ```tsx 'use client'; import { useOptimistic, useTransition } from 'react'; import { addToFavorites } from './actions'; function FavoriteButton({ productId, initialFavorited }) { const [isPending, startTransition] = useTransition(); // Optimistic State const [optimisticFavorited, addOptimistic] = useOptimistic( initialFavorited, (current, newValue: boolean) => newValue ); const handleClick = () => { // Sofort UI updaten addOptimistic(!optimisticFavorited); // Server-Action im Hintergrund startTransition(async () => { await addToFavorites(productId, !optimisticFavorited); }); }; return ( ); } ``` --- ## Pattern 4: Loading Hierarchies ### loading.tsx für Route-Level ```tsx // app/products/loading.tsx export default function ProductsLoading() { return (
{/* Header ist statisch */}
{/* Filter Bar Skeleton */}
{/* Product Grid Skeleton */}
); } ``` ### Hierarchische Loading States ```tsx // Layout mit persistentem Skeleton export default function ProductsLayout({ children }: { children: React.ReactNode }) { return (
{/* Immer sichtbar */} {/* Content lädt */}
{children}
); } ``` --- ## Pattern 5: Streaming mit LLM ### AI-Response Streaming UI ```tsx 'use client'; import { useStreamingChat } from '@/hooks/useStreamingChat'; function ChatInterface() { const { response, isStreaming, sendMessage } = useStreamingChat(); const [input, setInput] = useState(''); return (
{/* Streaming Response */} {(response || isStreaming) && (
{response} {isStreaming && ( )}
)}
{ e.preventDefault(); sendMessage(input); setInput(''); }} className="p-4 border-t" > setInput(e.target.value)} disabled={isStreaming} className="w-full p-2 border rounded" />
); } ``` ### Partial Content während Streaming ```tsx function AIAnalysisCard() { const { analysis, isAnalyzing, sections } = useAIAnalysis(); return (
{/* Sections erscheinen progressiv */} {sections.map((section, i) => (

{section.title}

{section.loaded ? (

{section.content}

) : (
)}
))} {isAnalyzing && (
Analysiere... {sections.filter(s => s.loaded).length}/{sections.length}
)}
); } ``` --- ## Pattern 6: Error Boundaries mit Fallback ```tsx // components/ErrorBoundary.tsx 'use client'; import { useEffect } from 'react'; export function ErrorBoundary({ error, reset }: { error: Error; reset: () => void; }) { useEffect(() => { console.error('Error:', error); }, [error]); return (

Etwas ist schiefgelaufen

{error.message}

); } // error.tsx für Route-Level export default function ProductsError({ error, reset }: { error: Error; reset: () => void; }) { return ; } ``` --- ## Performance-Metriken | Metrik | Blocking | Streaming | Verbesserung | |--------|----------|-----------|--------------| | **FCP** | 3.2s | 0.8s | 75% schneller | | **LCP** | 3.5s | 1.2s | 66% schneller | | **TTI** | 4.0s | 2.0s | 50% schneller | | **CLS** | 0.15 | 0.02 | 87% besser | --- ## Best Practices ### Do's ```tsx // ✅ Unabhängige Suspense Boundaries }> }> // ✅ Skeleton passt zur echten Komponente }> // ✅ Static Content außerhalb von Suspense
{/* Kein Suspense nötig */}