--- name: performance-optimization description: React re-render optimization, Three.js rendering performance, useMemo/useCallback, bundle size, 60 fps profiling, Lighthouse budgets license: MIT --- # Performance Optimization Skill ## Context Applies when building performance-sensitive features, optimizing re-renders, reducing draw calls, minimizing bundle size, fixing memory leaks, profiling bottlenecks, or implementing game loops. ## Rules 1. **60 fps target** — 16.67 ms per frame max in game rendering 2. **Minimize re-renders** — `React.memo`, `useMemo`, `useCallback` used deliberately after profiling 3. **Refs for 60 Hz mutations** — never `useState` for per-frame updates 4. **Batch draw calls** — group similar geometries/materials in Three.js 5. **InstancedMesh** for > 10 similar objects (particles, enemies, bullets) 6. **Optimize geometry** — low polycounts; LOD for distant objects; frustum culling 7. **Lazy-load assets** — textures/models/sounds on demand; preload only critical 8. **Code-split** — dynamic `import()` by route/feature; `React.lazy` + `Suspense` 9. **Memoize expensive calculations** — `useMemo` with stable deps 10. **Debounce/throttle** resize, scroll, input handlers 11. **Profile first** — React DevTools Profiler + Chrome Performance + Spector.js before optimizing 12. **Bundle budget** — < 500 KB gzipped initial load (matches `budget.json`) 13. **Tree-shake** — import individual symbols, not whole modules 14. **Audio**: reuse Howler instances; unload unused sounds 15. **Memory**: dispose Three.js resources on unmount; break ref cycles ## Lighthouse Budget (aligned with `budget.json`) | Metric | Target | |---|---| | Performance score | ≥ 90 | | LCP | < 2.5 s | | TTI | < 3.5 s | | CLS | < 0.1 | | Initial JS gzipped | < 500 KB | ## Examples ### ✅ Memoized leaf component ```tsx import { memo, useCallback, useMemo } from 'react'; interface HUDProps { score: number; health: number; onPause: () => void; } function GameHUDInner({ score, health, onPause }: HUDProps): JSX.Element { const healthColor = useMemo(() => (health > 50 ? '#0f0' : '#f00'), [health]); const handlePause = useCallback(() => onPause(), [onPause]); return (