veza/apps/web/dev_audit/frontend/07_performance_analysis.md
senke 5f88c56113 fix: UI remediation Phase 1 (S0-S5) + Phase 2 Sprint 6 shadow system
Phase 1:
- S0: Fix open redirect (safeNavigate), delete AuthContext/legacy auth, encrypt API keys, gitignore .env files
- S1: Split client.ts god object into 5 modules, unify toast system, delete unused Sidebar
- S2: Add glass button variant, migrate 32 z-index to SUMI tokens, fix card dark mode
- S3: Skip nav link, aria-hidden on icons, focus-visible ring fixes, alt attrs, aria-live regions
- S4: React.memo on list items, fix key={index}, loading=lazy on images
- S5: Branded loading screen, page transitions respect reduced-motion, LikeButton micro-interaction, i18n sidebar/header

Phase 2 Sprint 6:
- Wire Tailwind shadow utilities to SUMI tokens in @theme block (fixes 50+ files)
- Define shadow-card/shadow-card-hover tokens
- Remove dark:shadow-none workarounds from card.tsx (SUMI handles per-theme shadows)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 10:13:44 +01:00

7.7 KiB

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

// 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