utopia-ui/docs/PERFORMANCE_OPTIMIZATION_PLAN.md
2026-01-12 16:28:10 +01:00

410 lines
11 KiB
Markdown

# Performance-Optimierungsplan für Utopia Map
## Zusammenfassung der Analyse
Die Performance-Analyse hat drei Hauptbereiche mit Optimierungspotenzial identifiziert:
1. **React Component Re-Renders** - Unnötige Re-Renders durch Context-Architektur
2. **Map/Leaflet Performance** - Marker-Rendering und Clustering-Konfiguration
3. **Animations/Transitions** - CSS-Optimierungen für flüssigere Übergänge
---
## Kritische Probleme (Höchste Priorität)
### 1. MarkerClusterGroup Konfiguration
**Datei:** `lib/src/Components/Map/UtopiaMapInner.tsx:329-337`
```tsx
// PROBLEM: Alle Marker bleiben im DOM, auch wenn off-screen
removeOutsideVisibleBounds={false}
```
**Fix:** `removeOutsideVisibleBounds={true}` setzen
### 2. Marker ohne Memoization
**Datei:** `lib/src/Components/Item/PopupView.tsx:95-183`
- Alle Marker werden bei jedem State-Change neu gerendert
- Inline Event-Handler erstellen neue Funktionen pro Marker
- `MarkerIconFactory` wird für jeden Marker bei jedem Render aufgerufen
**Fix:**
- Marker-Komponente mit `React.memo` wrappen
- Event-Handler mit `useCallback` memoizen
- Icon-Erstellung mit `useMemo` cachen
### 3. Item-Mutation während Render
**Datei:** `lib/src/Components/Item/PopupView.tsx:98-106`
```tsx
// PROBLEM: Direkte Mutation des Item-Objekts
item.text += '\n\n'
item.tags.map((tag) => {
item.text += `#${encodeTag(tag)}`
})
```
**Fix:** Immutable Kopie erstellen oder Berechnung in useMemo verschieben
---
## Hohe Priorität
### 4. Context Provider Nesting
**Datei:** `lib/src/Components/AppShell/ContextWrapper.tsx:55-94`
10 verschachtelte Context-Provider verursachen Kaskaden-Re-Renders.
**Fix:**
- AppState in separate Contexts aufteilen (UI-State, Theme, Assets)
- `useMemo` für Context-Values verwenden
### 5. getItemTags O(n) Suche
**Datei:** `lib/src/Components/Map/hooks/useTags.tsx:92-118`
Lineare Suche für jeden Tag-Match bei jedem Item.
**Fix:** Map/Set für O(1) Lookups verwenden
### 6. ProfileForm teure Effect-Berechnung
**Datei:** `lib/src/Components/Profile/ProfileForm.tsx:93-143`
- Mehrere O(n) Array-Suchen
- Effect läuft bei jeder Items/Tags Änderung
**Fix:** `useMemo` für berechnete Werte
---
## Mittlere Priorität
### 7. SetAppState - 4 separate Effects
**Datei:** `lib/src/Components/AppShell/SetAppState.tsx:20-34`
4 separate Effects statt einem gebatchten Update.
**Fix:** Zu einem Effect zusammenfassen
### 8. Fehlende React.memo
- `HeaderView` - `lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView/index.tsx`
- `RelationCard` - `lib/src/Components/Profile/Subcomponents/RelationCard.tsx`
- `ItemViewPopup` - `lib/src/Components/Map/Subcomponents/ItemViewPopup.tsx`
- `FlexView` Template-Komponenten - `lib/src/Components/Profile/Templates/FlexView.tsx`
### 9. GalleryView ohne useMemo
**Datei:** `lib/src/Components/Profile/Subcomponents/GalleryView.tsx:22-39`
Images-Array wird bei jedem Render neu berechnet.
---
## Animation Optimierungen
### 10. Zu lange Transition-Zeiten
**Datei:** `app/src/App.css:18-21`
```css
/* PROBLEM: 1000ms ist zu langsam */
.transition-fade {
transition: opacity 1000ms ease;
}
```
**Fix:** Auf 300-400ms reduzieren
### 11. Margin statt Transform für Sidebar
**Dateien:**
- `lib/src/Components/AppShell/SideBar.tsx`
- `lib/src/Components/AppShell/Content.tsx`
```tsx
// PROBLEM: Margin-Transitions verursachen Layout-Reflows
tw:ml-48 tw:transition-all
// BESSER: GPU-beschleunigte Transforms
tw:translate-x-48 tw:transition-transform
```
### 12. Modal Animation Konflikte
**Datei:** `lib/src/Components/Gaming/Modal.tsx:23-24`
`tw:transition-none` auf modal-box überschreibt Container-Transition.
### 13. DialogModal DOM-Manipulation
**Datei:** `lib/src/Components/Templates/DialogModal.tsx`
Direkte style/classList Manipulation verursacht Layout-Thrashing.
---
## Empfohlene Implementierungsreihenfolge
### Phase 1: Quick Wins (Sofort umsetzbar)
1. `removeOutsideVisibleBounds={true}` setzen
2. App.css Transition von 1000ms auf 300ms reduzieren
3. SetAppState Effects zusammenfassen
### Phase 2: Memoization (Mittlerer Aufwand)
4. Marker-Komponente mit React.memo wrappen
5. MarkerIconFactory Ergebnisse cachen
6. HeaderView, RelationCard, ItemViewPopup mit React.memo
7. GalleryView images mit useMemo
### Phase 3: Architektur (Höherer Aufwand)
8. getItemTags auf Map-basierte Lookups umstellen
9. ProfileForm Berechnungen optimieren
10. Item-Mutation in PopupView beheben
### Phase 4: CSS/Animation
11. Sidebar Margin → Transform Migration
12. Modal Animation Konflikte beheben
13. will-change Hints hinzufügen
---
## Erwartete Verbesserungen
- **30-50% weniger Re-Renders** durch Memoization
- **Flüssigere Map-Interaktion** durch Marker-Optimierungen
- **Schnellere Übergänge** durch kürzere Transition-Zeiten
- **Weniger Layout-Thrashing** durch Transform statt Margin
---
## Kritische Dateien
| Datei | Änderungen |
|-------|------------|
| `UtopiaMapInner.tsx` | Cluster-Config, Event-Handler |
| `PopupView.tsx` | Marker-Memoization, Item-Mutation |
| `useTags.tsx` | Map-basierte Lookups |
| `ProfileForm.tsx` | useMemo für Berechnungen |
| `SetAppState.tsx` | Effects zusammenfassen |
| `App.css` | Transition-Zeiten |
| `SideBar.tsx` / `Content.tsx` | Transform statt Margin |
---
## Messstrategie & Erfolgsmetriken
### Baseline-Messung (vor Optimierungen)
#### 1. React DevTools Profiler
```bash
# Chrome Extension: React Developer Tools
# Profiler Tab → Record → Interaktionen durchführen → Stop
```
**Zu messende Szenarien:**
- [ ] Map laden mit 100+ Markern → Render-Zeit notieren
- [ ] Filter-Tag hinzufügen/entfernen → Re-Render-Count
- [ ] Sidebar öffnen/schließen → Render-Zeit
- [ ] Item-Popup öffnen → Zeit bis vollständig gerendert
- [ ] Zwischen Items navigieren → Transition-Smoothness
**Metriken:**
| Szenario | Baseline | Nach Phase 1 | Nach Phase 2 | Ziel |
|----------|----------|--------------|--------------|------|
| Initial Map Render | ___ ms | ___ ms | ___ ms | <500ms |
| Marker Re-Renders | ___ count | ___ count | ___ count | <10 |
| Tag Filter Toggle | ___ ms | ___ ms | ___ ms | <100ms |
| Sidebar Toggle | ___ ms | ___ ms | ___ ms | <50ms |
| Popup Open | ___ ms | ___ ms | ___ ms | <200ms |
#### 2. Chrome DevTools Performance Panel
```
F12 → Performance Tab → Record → Interaktionen → Stop
```
**Zu messen:**
- **FPS während Animationen** (Ziel: konstant 60 FPS)
- **Long Tasks** (>50ms) identifizieren
- **Layout Shifts** bei Sidebar/Modal
- **Scripting vs Rendering Zeit**
#### 3. Lighthouse Performance Score
```bash
# Chrome DevTools → Lighthouse → Performance
```
**Metriken:**
| Metrik | Baseline | Ziel |
|--------|----------|------|
| Performance Score | ___ | >90 |
| First Contentful Paint | ___ s | <1.5s |
| Largest Contentful Paint | ___ s | <2.5s |
| Total Blocking Time | ___ ms | <200ms |
| Cumulative Layout Shift | ___ | <0.1 |
#### 4. Custom Performance Markers (Optional)
```tsx
// Kann in kritischen Komponenten eingefügt werden:
useEffect(() => {
performance.mark('PopupView-render-start')
return () => {
performance.mark('PopupView-render-end')
performance.measure('PopupView-render',
'PopupView-render-start',
'PopupView-render-end')
console.log(performance.getEntriesByName('PopupView-render'))
}
}, [])
```
### Testszenarien für Vergleich
#### Szenario A: Große Datenmenge
1. Map mit 200+ Items laden
2. 3 Filter-Tags aktivieren
3. Zwischen 5 Items navigieren
4. Sidebar 3x öffnen/schließen
#### Szenario B: Schnelle Interaktionen
1. Rapid Tag-Toggle (5x schnell hintereinander)
2. Item-Popup öffnen schließen nächstes öffnen
3. Sidebar während Map-Pan öffnen
#### Szenario C: Animation Smoothness
1. Sidebar-Animation beobachten (ruckelt?)
2. Modal öffnen/schließen
3. Zwischen Profile-Views wechseln
### Erfolgs-Kriterien
| Phase | Erfolgskriterium |
|-------|------------------|
| Phase 1 (Quick Wins) | Lighthouse Score +10 Punkte, keine sichtbaren Ruckler bei Sidebar |
| Phase 2 (Memoization) | Re-Render Count -50%, Marker-Interaktion <100ms |
| Phase 3 (Architektur) | Initial Load <500ms bei 200 Items |
| Phase 4 (CSS) | Konstant 60 FPS bei allen Animationen |
### Automatisierte Performance-Tests mit Vitest
#### Setup
Neue Datei: `lib/src/__tests__/performance.bench.ts`
```typescript
import { describe, bench, expect } from 'vitest'
import { render } from '@testing-library/react'
import { performance } from 'perf_hooks'
// Utility für Performance-Messung
const measureRender = async (Component: React.FC, props: any) => {
const start = performance.now()
const { unmount } = render(<Component {...props} />)
const end = performance.now()
unmount()
return end - start
}
describe('Performance Benchmarks', () => {
// Marker Rendering
bench('render 100 markers', async () => {
const items = generateMockItems(100)
await measureRender(PopupView, { items })
}, { time: 1000 })
bench('render 500 markers', async () => {
const items = generateMockItems(500)
await measureRender(PopupView, { items })
}, { time: 2000 })
// Tag Filter Performance
bench('filter toggle with 100 items', async () => {
// Simuliert Tag-Filter Toggle
})
// Sidebar Animation
bench('sidebar open/close cycle', async () => {
// Misst Sidebar-Toggle
})
// ProfileForm Rendering
bench('ProfileForm with complex item', async () => {
const item = generateComplexItem()
await measureRender(ProfileForm, { item })
})
})
```
#### Vitest Config Erweiterung
```typescript
// vitest.config.ts - benchmark mode hinzufügen
export default defineConfig({
test: {
benchmark: {
include: ['**/*.bench.ts'],
reporters: ['default', 'json'],
outputFile: './benchmark-results.json',
},
},
})
```
#### NPM Script
```json
// package.json
{
"scripts": {
"test:perf": "vitest bench",
"test:perf:baseline": "vitest bench --outputFile=baseline.json",
"test:perf:compare": "vitest bench --compare=baseline.json"
}
}
```
#### Performance-Grenzwerte
```typescript
// lib/src/__tests__/performance.thresholds.ts
export const PERF_THRESHOLDS = {
MARKER_RENDER_100: 500, // ms
MARKER_RENDER_500: 2000, // ms
TAG_FILTER_TOGGLE: 100, // ms
SIDEBAR_TOGGLE: 50, // ms
POPUP_OPEN: 200, // ms
PROFILE_RENDER: 300, // ms
}
// In Tests verwenden:
expect(renderTime).toBeLessThan(PERF_THRESHOLDS.MARKER_RENDER_100)
```
#### CI Integration (Optional)
```yaml
# .github/workflows/performance.yml
name: Performance Benchmarks
on:
pull_request:
branches: [main]
jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:perf
- name: Compare with baseline
run: npm run test:perf:compare
```
### Dokumentation der Messungen
Vor jeder Phase:
1. Baseline-Werte in Tabelle eintragen
2. Screenshots von DevTools Profiler speichern
3. Lighthouse Report als PDF exportieren
Nach jeder Phase:
1. Neue Werte eintragen
2. Vergleich dokumentieren
3. Bei Regression: Ursache analysieren