Profiling and fixing a slow React render is easier when you stop guessing. Learn how to use React DevTools to find bottlenecks and optimize your app.

Last month, our dashboard’s sidebar became sluggish during heavy data updates, causing a noticeable delay whenever a user toggled a filter. We were seeing interaction times spike to around 350ms, which is unacceptable for a snappy UI, so I spent two days deep-diving into the render cycle to figure out exactly what was eating our main thread.
When your app grows, "it just works" stops being a viable strategy. You’ll eventually hit a wall where simple state updates trigger a cascade of re-renders across the entire component tree. Before you start wrapping everything in memo, you need to verify where the time is actually going.
I start by opening the React DevTools Profiler tab. I record a session, trigger the slow interaction, and stop the recording. Don't look at the flame graph first; look at the "Ranked" chart. It shows you exactly which components took the longest to render and, crucially, why they rendered. If a component re-rendered because its parent did, but its props didn't change, you’ve found your first target.

My first instinct was to wrap every child component in React.memo. It felt like a quick fix, but it actually made the codebase harder to maintain and didn't solve the core issue. I was treating the symptoms, not the disease.
The real problem was state colocation. We were keeping our filterState at the top level of the Dashboard component. Every time a user changed a filter, the entire Dashboard—including the massive DataGrid—re-rendered. By moving the filter state into a dedicated FilterProvider or simply pulling it down into the Sidebar component, I limited the scope of the re-render.
If you're struggling with similar performance issues, understanding Streaming and Suspense in Next.js: Optimize Your Page Load can help you offload heavy logic, but for internal state, you have to be disciplined about where that state lives.
Once I moved the state, I noticed the DataGrid was still re-rendering too often because of an unstable object reference passed as a prop. Here is how I refactored the parent component to stabilize the reference:
JAVASCRIPT// Before: Unstable reference causes re-render const Dashboard = () => { const [filters, setFilters] = useState({ category: CE9178">'all' }); // This object is recreated on every render! const config = { sort: CE9178">'asc' }; return <DataGrid config={config} />; }; // After: Using useMemo to stabilize the reference const Dashboard = () => { const [filters, setFilters] = useState({ category: CE9178">'all' }); const config = useMemo(() => ({ sort: CE9178">'asc' }), []); return <DataGrid config={config} />; };
This simple change prevented the DataGrid from re-rendering unless the config actually changed. Remember that Lists and keys in React: Why the console warnings matter is another common area where developers lose performance; if your keys are unstable, React has to destroy and recreate the entire list DOM, which is a massive performance killer.

It's tempting to try and get every component to render in under 1ms, but that's a trap. If an interaction feels fast to the user, it’s fast enough. I usually aim for an interaction to complete in under 100ms.
If you are seeing slow performance even after memoizing components, check if your data fetching is the culprit. We often try to fix UI renders when the real issue is how we are Fetching data in a React component the right way. If your API calls are blocking the main thread or causing excessive waterfall requests, no amount of useMemo will save you.
Q: Should I memoize every component by default? A: No. Memoization has a cost—React has to compare props on every render. Only memoize components that are expensive to render or re-render frequently due to parent state changes.
Q: How do I know if a component is "expensive"? A: Use the React DevTools Profiler. If a component takes more than 16ms to render, it’s a candidate for optimization.
Q: Does Next.js change how I should profile? A: Not fundamentally, but the Server/Client component boundary changes where you can apply these techniques. You can't memoize a Server Component, so focus your performance efforts on the Client Components where the interactivity lives.
I’m still not convinced we have the perfect balance in our current architecture. Sometimes I wonder if we’re over-engineering the state management instead of just simplifying the UI structure. Next time, I’ll probably start by removing features before I start adding performance optimizations. It’s usually faster to delete code than it is to optimize it.
Next.js Partial Prerendering (PPR) lets you mix static and dynamic UI in one route. Learn how to optimize your e-commerce product feeds for instant loading.
Read more