Add blog posts, cleanup unused files, update components

- Add 100 blog posts covering AI, development, and tech topics
- Add .env.example for environment configuration
- Add accessibility and lighthouse audit scripts
- Remove obsolete SEO reports and temporary files
- Remove dev-dist build artifacts and backup files
- Remove unused portrait images (moved/consolidated elsewhere)
- Update contact form and component improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 11:42:11 +01:00
co-authored by Claude Opus 4.5
parent cba6c66ccc
commit 43484c5023
164 changed files with 60969 additions and 23346 deletions
+495
View File
@@ -0,0 +1,495 @@
# 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 (
<main>
{/* Sofort gerendert - keine Suspense */}
<Header />
<Navigation />
{/* Schnelle Daten */}
<Suspense fallback={<StatsSkeleton />}>
<QuickStats />
</Suspense>
<div className="grid grid-cols-2 gap-4">
{/* Mittlere Latenz - unabhängig voneinander */}
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<TrafficChart />
</Suspense>
</div>
{/* Langsame Daten - lädt zuletzt */}
<Suspense fallback={<TableSkeleton rows={10} />}>
<DetailedReports />
</Suspense>
{/* Statisch */}
<Footer />
</main>
);
}
```
### Nested Suspense für feinere Kontrolle
```tsx
function ProductSection() {
return (
<section>
<Suspense fallback={<ProductListSkeleton />}>
<ProductList />
{/* Nested: Reviews laden nach Produkten */}
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews />
</Suspense>
</Suspense>
</section>
);
}
```
---
## Pattern 2: Skeleton Components
### Anatomy-preserving Skeletons
```tsx
// components/skeletons/ProductCardSkeleton.tsx
export function ProductCardSkeleton() {
return (
<div className="border rounded-lg p-4 animate-pulse">
{/* Bild */}
<div className="h-48 bg-gray-200 rounded-lg mb-4" />
{/* Titel */}
<div className="h-6 bg-gray-200 rounded w-3/4 mb-2" />
{/* Beschreibung */}
<div className="space-y-2 mb-4">
<div className="h-4 bg-gray-200 rounded w-full" />
<div className="h-4 bg-gray-200 rounded w-5/6" />
</div>
{/* Preis + Button */}
<div className="flex justify-between items-center">
<div className="h-8 bg-gray-200 rounded w-24" />
<div className="h-10 bg-gray-200 rounded w-28" />
</div>
</div>
);
}
// Grid Skeleton
export function ProductGridSkeleton({ count = 6 }) {
return (
<div className="grid grid-cols-3 gap-4">
{Array.from({ length: count }).map((_, i) => (
<ProductCardSkeleton key={i} />
))}
</div>
);
}
```
### 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 <div className={`skeleton-shimmer ${className}`} />;
}
```
---
## 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 (
<button
onClick={handleClick}
className={optimisticFavorited ? 'text-red-500' : 'text-gray-400'}
>
{optimisticFavorited ? '❤️' : '🤍'}
{isPending && <span className="ml-1">...</span>}
</button>
);
}
```
---
## Pattern 4: Loading Hierarchies
### loading.tsx für Route-Level
```tsx
// app/products/loading.tsx
export default function ProductsLoading() {
return (
<div>
{/* Header ist statisch */}
<div className="h-8 w-48 bg-gray-200 rounded mb-6" />
{/* Filter Bar Skeleton */}
<div className="flex gap-2 mb-4">
<div className="h-10 w-32 bg-gray-200 rounded" />
<div className="h-10 w-32 bg-gray-200 rounded" />
<div className="h-10 w-32 bg-gray-200 rounded" />
</div>
{/* Product Grid Skeleton */}
<ProductGridSkeleton count={9} />
</div>
);
}
```
### Hierarchische Loading States
```tsx
// Layout mit persistentem Skeleton
export default function ProductsLayout({
children
}: {
children: React.ReactNode
}) {
return (
<div>
{/* Immer sichtbar */}
<ProductsHeader />
<FilterSidebar />
{/* Content lädt */}
<main className="flex-1">
{children}
</main>
</div>
);
}
```
---
## 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 (
<div className="flex flex-col h-full">
<div className="flex-1 overflow-y-auto p-4">
{/* Streaming Response */}
{(response || isStreaming) && (
<div className="bg-gray-100 rounded-lg p-4">
{response}
{isStreaming && (
<span className="inline-block w-2 h-4 bg-blue-500 ml-1 animate-pulse" />
)}
</div>
)}
</div>
<form
onSubmit={(e) => {
e.preventDefault();
sendMessage(input);
setInput('');
}}
className="p-4 border-t"
>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={isStreaming}
className="w-full p-2 border rounded"
/>
</form>
</div>
);
}
```
### Partial Content während Streaming
```tsx
function AIAnalysisCard() {
const { analysis, isAnalyzing, sections } = useAIAnalysis();
return (
<div className="space-y-4">
{/* Sections erscheinen progressiv */}
{sections.map((section, i) => (
<div
key={i}
className={`transition-opacity duration-300 ${
section.loaded ? 'opacity-100' : 'opacity-0'
}`}
>
<h3>{section.title}</h3>
{section.loaded ? (
<p>{section.content}</p>
) : (
<div className="h-20 skeleton-shimmer rounded" />
)}
</div>
))}
{isAnalyzing && (
<div className="text-sm text-gray-500">
Analysiere... {sections.filter(s => s.loaded).length}/{sections.length}
</div>
)}
</div>
);
}
```
---
## 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 (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
<h2 className="text-red-800 font-semibold">
Etwas ist schiefgelaufen
</h2>
<p className="text-red-600 text-sm mt-1">
{error.message}
</p>
<button
onClick={reset}
className="mt-3 px-4 py-2 bg-red-100 text-red-800 rounded hover:bg-red-200"
>
Erneut versuchen
</button>
</div>
);
}
// error.tsx für Route-Level
export default function ProductsError({
error,
reset
}: {
error: Error;
reset: () => void;
}) {
return <ErrorBoundary error={error} reset={reset} />;
}
```
---
## 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
<Suspense fallback={<A />}><ComponentA /></Suspense>
<Suspense fallback={<B />}><ComponentB /></Suspense>
// ✅ Skeleton passt zur echten Komponente
<Suspense fallback={<ProductCardSkeleton />}>
<ProductCard />
</Suspense>
// ✅ Static Content außerhalb von Suspense
<Header /> {/* Kein Suspense nötig */}
<Suspense>
<DynamicContent />
</Suspense>
<Footer /> {/* Kein Suspense nötig */}
```
### Don'ts
```tsx
// ❌ Eine große Suspense für alles
<Suspense fallback={<FullPageSpinner />}>
<EntirePage />
</Suspense>
// ❌ Generischer Spinner statt Skeleton
<Suspense fallback={<Spinner />}>
<ComplexDashboard />
</Suspense>
// ❌ Suspense um statischen Content
<Suspense>
<StaticHeader />
</Suspense>
```
---
## Fazit
Streaming UI Patterns verbessern UX dramatisch:
1. **Sofortige Inhalte**: User sieht nie eine leere Seite
2. **Progressive Loading**: Wichtiges zuerst, Details später
3. **Perceived Performance**: App fühlt sich schneller an
4. **Resilience**: Langsame Teile blockieren nicht den Rest
Die Zukunft ist nicht "Laden oder Geladen" es ist ein Spektrum.
---
## Bildprompts
1. "Website loading progressively in sections, waterfall effect, UI/UX concept"
2. "Skeleton screens transforming into real content, animation sequence"
3. "User happily browsing while content streams in background, modern web experience"
---
## Quellen
- [React Suspense Documentation](https://react.dev/reference/react/Suspense)
- [Next.js Loading UI](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming)
- [Vercel: Streaming SSR](https://vercel.com/blog/streaming-server-rendering-with-suspense)
- [Web.dev: Skeleton Screens](https://web.dev/articles/skeleton-screens)