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>
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 :
hiddenen production (ne pas exposer le code source) ✅ - Bundle analyzer :
rollup-plugin-visualizeren 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 cardsPlaybackHeatmapGrid.tsx:35— grid dynamiqueTrackGrid.tsx:135— grille de tracksTrackSearchResults.tsx:89— résultats de rechercheChatMessage.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
QueryClientProviderest au niveau racine [main.tsx:218] — normal ThemeProvider,AudioProvider,ToastProvidersont au niveau App [App.tsx:170-187] — scope approprié- Pas de provider trop large provoquant des re-renders en cascade identifié
useEffect instances
- ~90
useEffectavec deps vides[]— mount-only effects - La plupart sont des initialisations (fetch, event listeners). Quelques uns pourraient avoir des dépendances manquantes mais ESLint
exhaustive-depsest configuré enwarn[eslint.config.js].
G4. Assets
Images
OptimizedImagecomposant 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 |