11 KiB
Performance-Optimierungsplan für Utopia Map
Zusammenfassung der Analyse
Die Performance-Analyse hat drei Hauptbereiche mit Optimierungspotenzial identifiziert:
- React Component Re-Renders - Unnötige Re-Renders durch Context-Architektur
- Map/Leaflet Performance - Marker-Rendering und Clustering-Konfiguration
- 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
// 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
MarkerIconFactorywird für jeden Marker bei jedem Render aufgerufen
Fix:
- Marker-Komponente mit
React.memowrappen - Event-Handler mit
useCallbackmemoizen - Icon-Erstellung mit
useMemocachen
3. Item-Mutation während Render
Datei: lib/src/Components/Item/PopupView.tsx:98-106
// 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)
useMemofü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.tsxRelationCard-lib/src/Components/Profile/Subcomponents/RelationCard.tsxItemViewPopup-lib/src/Components/Map/Subcomponents/ItemViewPopup.tsxFlexViewTemplate-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
/* 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.tsxlib/src/Components/AppShell/Content.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)
removeOutsideVisibleBounds={true}setzen- App.css Transition von 1000ms auf 300ms reduzieren
- SetAppState Effects zusammenfassen
Phase 2: Memoization (Mittlerer Aufwand)
- Marker-Komponente mit React.memo wrappen
- MarkerIconFactory Ergebnisse cachen
- HeaderView, RelationCard, ItemViewPopup mit React.memo
- GalleryView images mit useMemo
Phase 3: Architektur (Höherer Aufwand)
- getItemTags auf Map-basierte Lookups umstellen
- ProfileForm Berechnungen optimieren
- Item-Mutation in PopupView beheben
Phase 4: CSS/Animation
- Sidebar Margin → Transform Migration
- Modal Animation Konflikte beheben
- 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
# 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
# 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)
// 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
- Map mit 200+ Items laden
- 3 Filter-Tags aktivieren
- Zwischen 5 Items navigieren
- Sidebar 3x öffnen/schließen
Szenario B: Schnelle Interaktionen
- Rapid Tag-Toggle (5x schnell hintereinander)
- Item-Popup öffnen → schließen → nächstes öffnen
- Sidebar während Map-Pan öffnen
Szenario C: Animation Smoothness
- Sidebar-Animation beobachten (ruckelt?)
- Modal öffnen/schließen
- 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
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
// vitest.config.ts - benchmark mode hinzufügen
export default defineConfig({
test: {
benchmark: {
include: ['**/*.bench.ts'],
reporters: ['default', 'json'],
outputFile: './benchmark-results.json',
},
},
})
NPM Script
// 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
// 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)
# .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:
- Baseline-Werte in Tabelle eintragen
- Screenshots von DevTools Profiler speichern
- Lighthouse Report als PDF exportieren
Nach jeder Phase:
- Neue Werte eintragen
- Vergleich dokumentieren
- Bei Regression: Ursache analysieren