Using the React Profiler โ Finding Real Bottlenecks in Real Apps
Day 25 of 40 โ React System Design Series

Hi, Iโm Richa โ a Senior Frontend Engineer with 5+ years of experience building scalable, production-grade web interfaces for enterprise and consumer applications. I work primarily with React, TypeScript, and modern frontend architectures, focusing on component systems, performance, and maintainability. Most of my experience comes from building real-world products in regulated domains like banking and insurance, where clarity, reliability, and long-term ownership matter more than quick demos. Through this blog, I write about frontend engineering fundamentals, scalable UI design, problem-solving, and the lessons Iโve learned working on large codebases. My goal is to share practical insights โ not shortcuts โ for developers who want to grow strong engineering foundations. I also mentor early-career developers and strongly believe that curiosity, asking the right questions, and understanding why something works are more important than memorizing tools. If youโre serious about improving as an engineer, youโre in the right place.
Hey everyone, Richa here! ๐

Day 25. Last day of Week 5.
And today we wrap up the performance week with the tool that ties everything together.
The React Profiler.
Here's the thing โ everything we've covered this week (code splitting, virtualisation, bundle optimisation, memoisation on Day 5) has one thing in common: you should only apply them where there's an actual, measurable problem.
The React Profiler is how you find those problems. It shows you exactly which components are rendering, why they're rendering, and how long each render takes.
Without it, you're guessing. With it, you're diagnosing.
Two profilers โ know both

React DevTools Profiler โ the browser extension, for runtime render profiling
The <Profiler> API โ built into React, for measuring programmatically
We'll cover both. Start with the DevTools one โ it's what you'll use 90% of the time.
React DevTools Profiler โ getting started

Install the React DevTools browser extension (Chrome or Firefox). It adds two tabs to DevTools: Components and Profiler.
The Profiler tab workflow:
Open DevTools โ Profiler tab
Click the Record button (circle)
Interact with your app โ navigate, click, type
Click Stop (square)
Inspect the flame graph
The flame graph shows every render that happened during the recording โ which component rendered, how long it took, and what triggered it.
Reading the flame graph
App (0.1ms)
โโโ Layout (0.2ms)
โโโ Navbar (0.1ms)
โโโ Sidebar (0.3ms)
โโโ ProductsPage (12.4ms) โ this is slow
โโโ ProductFilters (0.4ms)
โโโ ProductGrid (11.8ms) โ almost all the time is here
โโโ ProductCard (0.2ms)
โโโ ProductCard (0.2ms)
โโโ ProductCard (0.2ms)
... ร 200 cards
What the colours mean:
Grey โ component didn't re-render this cycle
Blue/teal โ rendered, reasonable time
Yellow/orange โ rendered, slower
Red โ rendered, slow โ investigate
What to look for:
Components that are grey when they should be (not re-rendering unnecessarily) โ
Components that are coloured every time even when nothing changed โ
Components with unexpectedly long render times
Components that render many times for one user action
Finding unnecessary re-renders

This is the most common use case. You suspect a component is re-rendering too often. The Profiler tells you exactly when and why.
After recording, click on any rendered component in the flame graph. The right panel shows:
Why did this render?
โ Props changed
โ onAddToCart: [function] โ [function] โ different function reference!
This tells you: ProductCard re-rendered because onAddToCart was a new function reference every time the parent rendered.
The fix from Day 5: useCallback on the function.
// โ Before โ new function reference every render
function ProductGrid({ products }: { products: Product[] }) {
const addToCart = useCartStore(state => state.addItem);
return products.map(p => (
<ProductCard
key={p.id}
product={p}
onAddToCart={() => addToCart(p)} // โ new arrow function every render
/>
));
}
// โ
After โ stable function reference
function ProductGrid({ products }: { products: Product[] }) {
const addToCart = useCartStore(state => state.addItem);
const handleAddToCart = useCallback((product: Product) => {
addToCart(product);
}, [addToCart]);
return products.map(p => (
<MemoizedProductCard
key={p.id}
product={p}
onAddToCart={handleAddToCart}
/>
));
}
const MemoizedProductCard = React.memo(ProductCard);
Profile again โ the ProductCard components should now be grey (not re-rendering) when the parent renders for unrelated reasons.
The <Profiler> API โ measuring in code
For more targeted measurement โ especially when you want numbers logged to the console or sent to monitoring:
import { Profiler, type ProfilerOnRenderCallback } from 'react';
const onRenderCallback: ProfilerOnRenderCallback = (
id, // the "id" prop of the Profiler
phase, // "mount" or "update"
actualDuration, // time spent rendering the committed update
baseDuration, // estimated time to render entire subtree without memoisation
startTime, // when React began rendering the update
commitTime, // when React committed the update
) => {
// Log slow renders
if (actualDuration > 16) { // 16ms = 1 frame at 60fps
console.warn(`Slow render: \({id} took \){actualDuration.toFixed(2)}ms (${phase})`);
}
// Or send to monitoring (Datadog, Sentry, custom analytics)
if (actualDuration > 50) {
analytics.track('slow_render', {
component: id,
phase,
duration: actualDuration,
});
}
};
function ProductsPage() {
return (
<Profiler id="ProductsPage" onRender={onRenderCallback}>
<ProductFilters />
<ProductGrid />
</Profiler>
);
}
actualDuration > 16ms means the component is slower than one frame at 60fps โ a good threshold for "investigate this."
A complete profiling workflow โ finding and fixing a real issue
Here's how I approach a performance complaint in production:
Step 1 โ Reproduce the issue
User says: "the dashboard feels slow when I type in the search box."
Open DevTools โ Profiler. Start recording. Type in the search box. Stop.
Step 2 โ Read the flame graph
DashboardPage (0.3ms)
SearchBar (0.1ms) โ the component being typed in
StatsSummary (4.2ms) โ why is this re-rendering?
UserActivityChart (8.7ms) โ and this?
RecentOrdersTable (11.3ms) โ and this!
Three heavy components re-rendering on every keystroke in the search bar.
Step 3 โ Check why they're re-rendering
Click StatsSummary in the Profiler. "Why did this render?"
Why did this render?
โ Parent component rendered
DashboardPage re-renders when the search state changes โ all its children re-render.
Step 4 โ Apply the fix
// Before โ everything re-renders when searchQuery changes
function DashboardPage() {
const [searchQuery, setSearchQuery] = useState('');
return (
<div>
<SearchBar value={searchQuery} onChange={setSearchQuery} />
<StatsSummary /> {/* re-renders on every keystroke */}
<UserActivityChart /> {/* re-renders on every keystroke */}
<RecentOrdersTable /> {/* re-renders on every keystroke */}
</div>
);
}
// After โ heavy components memoised, only re-render if their props change
const MemoStatsSummary = React.memo(StatsSummary);
const MemoUserActivityChart = React.memo(UserActivityChart);
const MemoRecentOrdersTable = React.memo(RecentOrdersTable);
function DashboardPage() {
const [searchQuery, setSearchQuery] = useState('');
return (
<div>
<SearchBar value={searchQuery} onChange={setSearchQuery} />
<MemoStatsSummary /> {/* only re-renders if its own props change */}
<MemoUserActivityChart />
<MemoRecentOrdersTable />
</div>
);
}
Step 5 โ Profile again to verify
Record. Type in the search box. Stop.
DashboardPage (0.3ms)
SearchBar (0.1ms) โ still updating (correct)
StatsSummary (grey) โ not re-rendering โ
UserActivityChart (grey) โ not re-rendering โ
RecentOrdersTable (grey) โ not re-rendering โ
Fixed. And we can see it in the Profiler โ not just assume it.
Performance marks โ custom timing
For more precise measurement of specific operations:
// Mark the start and end of a user interaction
function handleSearch(query: string) {
performance.mark('search-start');
setSearchQuery(query);
// After React renders
requestAnimationFrame(() => {
performance.mark('search-end');
performance.measure('search-render', 'search-start', 'search-end');
const measure = performance.getEntriesByName('search-render')[0];
console.log(`Search render: ${measure.duration.toFixed(2)}ms`);
});
}
You can see these custom marks in DevTools โ Performance tab alongside React's own renders.
The golden rules of profiling

Rule 1 โ Always measure before optimising
Don't add React.memo or useMemo because you think it might help. Profile first. Confirm the problem. Then apply the fix. Then profile again to confirm the fix worked.
Rule 2 โ Profile on realistic hardware
Chrome DevTools โ Performance tab has a CPU throttle setting. Set it to 4x or 6x slowdown to simulate mid-range Android performance. A fast MacBook Pro hides performance problems that real users face.
Rule 3 โ Profile in production mode
React's development mode has extra overhead (warnings, checks, etc.). Profile your production build for accurate numbers:
npm run build
npm run preview # serves the production build locally
Rule 4 โ Fix the worst first
The Profiler shows everything. Start with the slowest component or most frequent unnecessary render. Fix that. Profile again. Repeat.
Rule 5 โ Grey is good
In the flame graph โ grey means "didn't render." More grey = fewer unnecessary renders. After a round of optimisation, you want to see much more grey than before.
Week 5 wrap-up

This week we covered:
Day 21 โ Rendering patterns: CSR, SSR, SSG, ISR โ the decision framework
Day 22 โ Code splitting:
React.lazy,Suspense, dynamic importsDay 23 โ List virtualisation: TanStack Virtual for 5,000+ row lists
Day 24 โ Bundle optimisation: tree shaking, lighter libraries, the visualiser
Day 25 โ React Profiler: finding and fixing real render bottlenecks
Next week: System Design Interview Patterns โ the scenarios they actually ask at senior/staff interviews. Real problems, real solutions.
See you Monday for Day 26 โ System design: Design a social media feed (infinite scroll, caching).
โ Day 24 โ Bundle Optimisation โ Day 26 โ Design a Social Media Feed
Part of the React System Design Series โ 40 days, one topic per day.






