Open DevTools → Performance → Record → interact with the app (drag sliders, scroll gallery, draw on canvas) → Stop. Read the flame chart: identify long tasks (>50ms blocks the main thread). Find the top 3 offenders: filter reapplication, gallery rendering, state serialization.
// ❌ Before: fires on every pixel
// of slider movement (100+ times/drag)
slider.addEventListener('input',
applyFilters);
// ✅ After: throttle to 60fps
function throttle(
fn: Function, limit: number
) {
let lastCall = 0;
return function(...args: any[]) {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn(...args);
}
};
}
slider.addEventListener('input',
throttle(applyFilters, 16)); // ~60fps
// ❌ Before: renders ALL 500 thumbnails
// (500 DOM nodes, most offscreen)
// ✅ After: only render visible items
import { useVirtualizer }
from '@tanstack/react-virtual';
function VirtualizedGallery({
images
}: { images: Image[] }) {
const parentRef = useRef(null);
const virtualizer = useVirtualizer({
count: images.length,
getScrollElement:
() => parentRef.current,
estimateSize: () => 200,
});
return (
<div ref={parentRef}
className="h-full overflow-auto">
<div style={{
height:
`${virtualizer.getTotalSize()}px`,
position: 'relative',
}}>
{virtualizer.getVirtualItems()
.map(virtualItem => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: `${virtualItem.start}px`,
height:
`${virtualItem.size}px`,
width: '100%',
}}>
<ImageCard
image={images[
virtualItem.index]}
/>
</div>
))}
</div>
</div>
);
}
// Take heap snapshots over time:
// objects growing = leak
// ❌ Common leaks:
useEffect(() => {
const interval = setInterval(
checkStatus, 1000);
window.addEventListener(
'resize', handleResize);
socket.on('update', handleUpdate);
// No cleanup! These accumulate
// on every re-render.
});
// ✅ Proper cleanup:
useEffect(() => {
const interval = setInterval(
checkStatus, 1000);
window.addEventListener(
'resize', handleResize);
socket.on('update', handleUpdate);
return () => {
clearInterval(interval);
window.removeEventListener(
'resize', handleResize);
socket.off('update', handleUpdate);
};
}, []);
// Run Lighthouse: DevTools → Lighthouse
// Score before AND after optimization
// Performance budgets:
First Contentful Paint: < 1.5s
Largest Contentful Paint: < 2.5s
Cumulative Layout Shift: < 0.1
Total Blocking Time: < 200ms
Bundle size (gzipped): < 200KB
git switch -c perf/PIXELCRAFT-077-profiling
git add src/
git commit -m "Fix 3 perf bottlenecks: throttle + virtualize + leak (PIXELCRAFT-077)"
git push origin perf/PIXELCRAFT-077-profiling
# PR → Review → Merge → Close ticket ✅
Amdahl's Law: always optimize the biggest bottleneck.
If 80% of time is spent in filter processing, optimizing everything else can only improve total time by 20%. Profile first. Guess never.