The component works. The data loads. The layout looks right. But when you type in the search box, every keystroke lags. When you scroll the list, the page stutters. When you leave the tab open for an hour, it uses 2GB of memory. These are performance problems, and AI tools are notorious for introducing them because they optimize for "code that works" rather than "code that runs efficiently."
The Performance panel reveals exactly what the browser is doing, and wasting time on, during any interaction.
Recording a performance profile
The workflow is: start recording, do the thing that feels slow, stop recording.
- Open the Performance panel (DevTools → Performance tab)
- Click the circle (Record) button, or press Cmd+E / Ctrl+E
- Perform the slow interaction (scroll, type, click, navigate)
- Click Stop (or press Cmd+E again)
- Wait a few seconds while DevTools analyzes the recording
Keep recordings short, 3 to 5 seconds. Longer recordings are harder to analyze and generate huge flame charts.
// If you want to profile something that happens on page load:
// Check "Screenshots" in the Performance panel toolbar
// Click the reload button (circle-arrow icon) instead of Record
// DevTools will record from the moment the page starts loadingReading the flame chart
The flame chart is the main visualization. Here is how to interpret it:
| Visual element | What it means |
|---|---|
| Bar width | Duration, wider bars took longer to execute |
| Yellow bars | JavaScript execution |
| Purple bars | Rendering (layout calculations, style recalculation) |
| Green bars | Painting (drawing pixels to the screen) |
| Stacked bars | Call stack, bars below were called by the bar above |
| Red triangle on a bar | Long task warning, this task blocked the main thread for over 50ms |
The strategy: find the widest yellow bars (long JavaScript tasks), click on them to see the function name, then look at the stacked bars underneath to understand what was being called.
Common AI-generated performance problems
Problem 1: re-rendering on every keystroke
This is the single most common performance issue in AI-generated React code. ChatGPT and Copilot routinely generate components that trigger a full re-renderWhat is re-render?When React calls a component function again to check if the UI needs updating, triggered by state or context changes. of a large list on every keystroke in a search input.
// Copilot generated this - it re-renders 1000 items per keystroke
function SearchableList({ items }) {
const [query, setQuery] = useState('');
// This filters on every render - fine
const filtered = items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
/>
{/* Problem: rendering 1000 items on every keystroke */}
{filtered.map(item => (
<ExpensiveItemCard key={item.id} item={item} />
))}
</div>
);
}In the Performance panel, you would see a tall stack of yellow bars firing every 50-100ms (once per keystroke), each containing hundreds of ExpensiveItemCard render calls. The fix involves useMemo, React.memo, or debouncing the input.
// Fixed version - memoize filtered results and card rendering
function SearchableList({ items }) {
const [query, setQuery] = useState('');
const filtered = useMemo(
() => items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
),
[items, query]
);
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
/>
{filtered.map(item => (
<MemoizedItemCard key={item.id} item={item} />
))}
</div>
);
}
const MemoizedItemCard = React.memo(ExpensiveItemCard);Problem 2: forced synchronous layout
AI-generated code frequently reads a layout property (like offsetHeight) immediately after writing a style, forcing the browser to recalculate layout synchronously instead of batchingWhat is batching?Grouping multiple state updates together into a single re-render cycle instead of re-rendering after each individual update..
// Claude generated this animation - it causes layout thrashing
function animateElements(elements) {
for (const el of elements) {
const height = el.offsetHeight; // Read - forces layout
el.style.marginTop = height + 'px'; // Write - invalidates layout
// Next iteration: read forces ANOTHER layout calculation
}
}
// Fixed: batch reads first, then writes
function animateElements(elements) {
const heights = elements.map(el => el.offsetHeight); // All reads
elements.forEach((el, i) => {
el.style.marginTop = heights[i] + 'px'; // All writes
});
}The flame chart shows this as "Forced reflow" in purple, often appearing hundreds of times in a loop. Each one takes 1-5ms, but in a loop of 100 elements, that is 100-500ms of total blocking time.
Problem 3: memory leaks from missing cleanup
AI tools consistently forget to clean up event listeners, intervals, and subscriptions. The code works on first load, but memory usage grows every time the component re-mounts.
// ChatGPT generated this React component - it leaks memory
function LiveClock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
// BUG: interval is never cleared!
setInterval(() => setTime(new Date()), 1000);
}, []);
return <span>{time.toLocaleTimeString()}</span>;
}
// Fixed: return a cleanup function
function LiveClock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(id); // Cleanup on unmount
}, []);
return <span>{time.toLocaleTimeString()}</span>;
}To detect memory leaks: open the Memory panel, take a heap snapshot, interact with your app (navigate away and back several times), take another snapshot, then compare them. If the same type of object keeps growing in count, that is your leak.
useEffect that sets up listeners, timers, or subscriptions, immediately check: does it return a cleanup function? If not, it will leak. This is the single most common AI React bug.Lighthouse, automated performance audits
Lighthouse is an automated auditing tool built into DevTools. It loads your page in a controlled environment and scores it across multiple categories:
| Metric | What it measures | Good score |
|---|---|---|
| FCP (First Contentful Paint) | When the user first sees content | Under 1.8s |
| LCP (Largest Contentful Paint) | When the main content finishes loading | Under 2.5s |
| TBT (Total Blocking Time) | How long the main thread was blocked | Under 200ms |
| CLS (Cumulative Layout Shift) | How much content jumps around during load | Under 0.1 |
| Speed Index | How quickly the visible area is populated | Under 3.4s |
Run Lighthouse in an Incognito window to avoid interference from browser extensions. AI-generated bundleWhat is bundle?A single JavaScript file (or set of files) that a build tool creates by combining all your source code and its imports together. sizes are often larger than necessary because AI imports entire libraries when only one function is needed, Lighthouse will flag this under "Reduce unused JavaScript."
Quick reference
| Tool | Where to find it | What it reveals about AI code |
|---|---|---|
| Performance panel | DevTools → Performance | Re-renders, layout thrashing, long tasks |
| Flame chart | Inside a Performance recording | Which functions are slow and what they call |
| Memory panel | DevTools → Memory | Leaked event listeners, uncleaned intervals |
| Lighthouse | DevTools → Lighthouse | Bundle size, load speed, unused code |
| React DevTools Profiler | React DevTools extension → Profiler | Which React components re-render and why |