veza/apps/web/dev_audit/frontend/07_performance_analysis.md

187 lines
7.7 KiB
Markdown
Raw Normal View History

# Phase G — Performance Analysis
**Score Performance : 6.5/10**
---
## G1. Bundle
### Configuration build
[vite.config.ts:68-92]
- **Target** : `esnext` — navigation moderne uniquement
- **Minification** : `esbuild`
- **Source maps** : `hidden` en production (ne pas exposer le code source) ✅
- **Bundle analyzer** : `rollup-plugin-visualizer` en production → `dist/bundle-analysis.html`
- **Chunk size warning** : 1000KB
### Manual chunks
```typescript
// vite.config.ts:73-87
manualChunks: {
'vendor-react': ['react', 'react-dom'],
'vendor-router': ['react-router'],
'vendor-tanstack': ['@tanstack/*'],
'vendor-icons': ['lucide-react'],
'vendor-utils': ['date-fns', 'zod'],
'vendor': // Default vendor chunk
}
```
**Positif** : Chunking explicite des dépendances pour un caching optimal. Les chunks vendeur changent rarement → longue durée de cache.
### Taille estimée
[DONNÉES INSUFFISANTES — `npm run build` non exécuté. Estimation basée sur les dépendances :]
| Chunk | Estimation |
|-------|-----------|
| `vendor-react` | ~140KB (gzip) |
| `vendor-router` | ~25KB |
| `vendor-tanstack` | ~40KB |
| `vendor-icons` (lucide-react) | ~50-80KB (226 fichiers importent des icônes) |
| `vendor-utils` (date-fns + zod) | ~30KB |
| `vendor` (reste) | ~100KB+ (axios, framer-motion, i18next, etc.) |
| Application code | ~200-300KB |
| **Total estimé** | **~600-800KB** (gzip) |
**Attention** : `lucide-react` (0.321.x) peut être volumineux si le tree-shaking n'est pas optimal. Les imports nommés (`import { Home } from 'lucide-react'`) sont utilisés → tree-shaking devrait fonctionner.
**Attention** : `framer-motion` (12.29.x) est une dépendance lourde (~60KB gzip). Vérifier si toutes les animations nécessitent framer-motion ou si CSS animations (déjà définies dans index.css) suffiraient.
---
## G2. Code splitting
### React.lazy
- **5 fichiers** utilisent `React.lazy()` / `lazy()` directement :
- `features/chat/components/ChatInput.tsx:31` — emoji-picker-react ✅
- `features/chat/components/ChatMessage.tsx:11` — emoji-picker-react ✅
- `components/ui/ImageCropper.tsx:4` — Cropper ✅
- `components/ui/lazy-component/createLazyComponent.tsx:48` — factory function ✅
- `components/feedback/LazyToaster.tsx:15` — react-hot-toast ✅
### Routes lazy-loadées
**Toutes les routes** sont lazy via `createLazyComponent` [components/ui/lazy-component/createLazyComponent.tsx:48] qui utilise `React.lazy` avec error boundaries. Les exports sont centralisés dans `lazy-component/lazyExports.ts` [LazyComponent.tsx:39 → lazyExports.ts].
Routes lazy : Login, Register, Dashboard, Chat, Library, Profile, Settings, Sessions, Roles, TrackDetail, Playlists, Marketplace, Search, Notifications, Analytics, Webhooks, Admin, Social, Seller, Wishlist, Purchases, DesignSystem, 404, 500. ✅ **Excellent.**
### Dynamic imports
- **60+ occurrences** de `import()` — usage approprié pour :
- Route splitting
- Heavy components (emoji-picker, cropper, swagger-ui)
- Conditional loading (MSW, Sentry, toast)
- Store lazy loading
---
## G3. Optimisation du rendu
### Memoization
| Pattern | Occurrences | Verdict |
|---------|------------|---------|
| `useMemo` / `useCallback` | 135 fichiers | ✅ Usage correct |
| `React.memo` | **5 fichiers seulement** | ⚠️ Insuffisant |
**Composants avec `React.memo`** :
- `PlaylistCard.tsx:203`
- `TrackCard.tsx:198`
- `CourseCard.tsx:136`
- `PostCard.tsx:342`
- `ProductCard.tsx:163`
**Problème** : Seuls les cards sont mémorisés. Les composants de liste (TrackList, PlaylistList, etc.) qui itèrent sur ces cards ne sont PAS mémorisés. Les re-renders du parent provoquent des re-renders de tous les enfants. **-1 point.**
### `key={index}` anti-pattern
**~55 occurrences** de `key={index}` ou `key={i}` ⚠️
Fichiers critiques (listes qui peuvent être réordonnées) :
- `DashboardPage.tsx:248,303` — dashboard cards
- `PlaybackHeatmapGrid.tsx:35` — grid dynamique
- `TrackGrid.tsx:135` — grille de tracks
- `TrackSearchResults.tsx:89` — résultats de recherche
- `ChatMessage.tsx:98` — messages chat
**Impact** : Pour les listes statiques (skeletons, options fixes), `key={index}` est acceptable. Pour les listes dynamiques (tracks, messages, résultats), c'est un bug potentiel de réconciliation React.
### Context providers
- Le `QueryClientProvider` est au niveau racine [main.tsx:218] — normal
- `ThemeProvider`, `AudioProvider`, `ToastProvider` sont au niveau App [App.tsx:170-187] — scope approprié
- Pas de provider trop large provoquant des re-renders en cascade identifié
### `useEffect` instances
- **~90 `useEffect` avec deps vides** `[]` — mount-only effects
- La plupart sont des initialisations (fetch, event listeners). Quelques uns pourraient avoir des dépendances manquantes mais ESLint `exhaustive-deps` est configuré en `warn` [eslint.config.js].
---
## G4. Assets
### Images
- **`OptimizedImage`** composant dédié (145L) avec IntersectionObserver pour lazy loading [optimized-image/OptimizedImage.tsx]
- **`BlurPlaceholder`** (33L) pour le placeholder pendant le chargement [optimized-image/BlurPlaceholder.tsx]
- **`loading="lazy"` natif** : 0 occurrences ❌ — repose entièrement sur le JS custom
- **WebP/AVIF** : Pas de détection de format optimisé côté client [DONNÉES INSUFFISANTES — dépend du backend/CDN]
### Fonts
- 4 familles : Inter, Space Grotesk, JetBrains Mono, Noto Serif JP [index.css:78-81]
- **Preload/display** : [DONNÉES INSUFFISANTES — nécessite vérification index.html]
- **Subset** : Non détecté — potentiellement 4 polices complètes téléchargées
### SVG
- 5 fichiers SVG dans `src/` — minimal
- Lucide React injecte les icônes en inline SVG → pas de requêtes HTTP supplémentaires ✅
---
## G5. Requêtes réseau
### Patterns de fetch
- **Request deduplication** : `services/requestDeduplication.ts` — évite les appels API dupliqués ✅
- **Response caching** : `services/responseCache.ts` — cache en mémoire avec TTL ✅
- **Offline queue** : `services/offlineQueue.ts` — queue les mutations offline et les rejoue ✅
- **React Query cache** : staleTime 1min, gcTime 5min [main.tsx:49-50]
- **Cross-tab sync** : `utils/reactQuerySync.ts` — synchronise le cache React Query entre onglets ✅
### WebSocket
- `services/websocket.ts` — WebSocket natif avec reconnection automatique ✅
- `features/streaming/hooks/usePlaybackRealtime.ts` (496L) — streaming temps réel avec analytics ✅
### Waterfall
- Les routes sont lazy-loadées, ce qui crée un waterfall initial (HTML → JS chunk → data fetch). C'est le pattern standard React SPA.
- `useRoutePreload.ts` (239L) implémente le **prefetching des routes** au hover des liens ✅ — réduit le waterfall perçu.
---
## Score Performance détaillé
| Critère | Points | Justification |
|---------|--------|---------------|
| Code splitting routes | +2 | Toutes les routes lazy, 60+ dynamic imports |
| Manual chunks vendeur | +1 | Bon chunking pour cache long-terme |
| React.memo insuffisant | -1 | 5 composants seulement, listes non mémorisées |
| key={index} | -0.5 | 55 occurrences, certaines sur des listes dynamiques |
| Request dedup + cache | +1 | Architecture réseau mature |
| Route prefetching | +0.5 | useRoutePreload au hover |
| useMemo/useCallback | +0.5 | 135 fichiers — bonne adoption |
| Pas de loading="lazy" | -0.5 | Repose sur JS, pas d'attribut natif |
| Fonts non optimisées | -0.5 | 4 familles, pas de subset/preload vérifié |
| Offline queue | +0.5 | Fonctionnalité avancée |
| framer-motion overhead | -0.5 | Dépendance lourde potentiellement surutilisée |
| **Total** | **6.5/10** | **Infrastructure solide, optimisation rendu lacunaire** |