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
Applies when building performance-sensitive features, optimizing re-renders, reducing draw calls, minimizing bundle size, fixing memory leaks, profiling bottlenecks, or implementing game loops.
60 fps target — 16.67 ms per frame max in game rendering
Minimize re-renders — React.memo, useMemo, useCallback used deliberately after profiling
Refs for 60 Hz mutations — never useState for per-frame updates
Batch draw calls — group similar geometries/materials in Three.js
InstancedMesh for > 10 similar objects (particles, enemies, bullets)
Optimize geometry — low polycounts; LOD for distant objects; frustum culling
Lazy-load assets — textures/models/sounds on demand; preload only critical
Code-split — dynamic import() by route/feature; React.lazy + Suspense
Memoize expensive calculations — useMemo with stable deps
Debounce/throttle resize, scroll, input handlers
Profile first — React DevTools Profiler + Chrome Performance + Spector.js before optimizing
Bundle budget — < 500 KB gzipped initial load (matches budget.json)
Tree-shake — import individual symbols, not whole modules
Audio : reuse Howler instances; unload unused sounds
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
✅ Memoized leaf component
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 (
< div className = "hud" >
< span > Score: { score } </ span >
< span style = { { color : healthColor } } > Health: { health } %</ span >
< button onClick = { handlePause } > Pause</ button >
</ div >
) ;
}
export const GameHUD = memo ( GameHUDInner ) ;
✅ Ref-based animation — zero re-renders
import { useRef } from 'react' ;
import { useFrame } from '@react-three/fiber' ;
import * as THREE from 'three' ;
function Spinner ( ) : JSX . Element {
const mesh = useRef < THREE . Mesh > ( null ) ;
useFrame ( ( _ , delta ) => {
if ( mesh . current ) mesh . current . rotation . y += delta ;
} ) ;
return < mesh ref = { mesh } > < boxGeometry /> < meshStandardMaterial /> </ mesh > ;
}
✅ Stable references outside render
const ORIGIN = [ 0 , 0 , 0 ] as const ;
function At ( ) : JSX . Element {
return < mesh position = { ORIGIN } > < sphereGeometry /> </ mesh > ;
}
✅ Code-splitting a heavy overlay
import { lazy , Suspense } from 'react' ;
const HelpOverlay = lazy ( ( ) => import ( './HelpOverlay' ) ) ;
function App ( ) : JSX . Element {
return (
< Suspense fallback = { null } >
< HelpOverlay />
</ Suspense >
) ;
}
// BAD: useState in useFrame — 60 re-renders/sec
useFrame ( ( ) => setRotation ( r => r + 0.01 ) ) ;
// BAD: new object every render
< mesh position = { [ 0 , 0 , 0 ] } > …</ mesh >
// BAD: unmemoized expensive calc inside render
const sorted = data . sort ( ( a , b ) => a - b ) ;
// BAD: importing entire lodash
import _ from 'lodash' ;
// BAD: forgetting to dispose geometry / material / texture
Measure before — React Profiler (component renders), Performance panel (frame times), Spector.js (draw calls)
Identify the hottest component or the longest task
Hypothesize (re-render? layout thrash? draw calls? allocation?)
Change one thing and re-measure
Record evidence in the PR (screenshots, numbers, flamegraphs)
Forgotten setInterval / subscriptions → cleanup in useEffect return
Undisposed THREE.Geometry / Material / Texture → dispose on unmount
Global event listeners → removeEventListener in cleanup
Closures capturing large data → useRef or restructure