A practical deep-dive into the most impactful React performance techniques — from lazy loading and memo to profiling tools and avoiding common pitfalls.
React is fast by default. But as applications grow, performance can silently degrade — unnecessary re-renders, oversized bundles, and blocking data fetches accumulate. This post covers the patterns I reach for first when improving React performance in production apps.
Every import in your entry file adds to your initial bundle. React.lazy lets you defer loading a component until it's actually needed. Combined with Suspense, you get a clean loading fallback with no extra libraries.
const Dashboard = React.lazy(() => import('./Dashboard'))
function App() {
return (
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
)
}In Next.js, next/dynamic handles this with SSR support built in. But the principle is the same: don't ship what you don't need on the initial render.
React.memo, useMemo, and useCallback add comparison overhead. The rule I follow: only memoize when you have a measured performance problem. Wrapping everything by default creates noise and false confidence.
Measure first. React DevTools Profiler shows exactly which components are re-rendering and why. Optimise the slow ones, not the ones that feel slow.
The most common source of avoidable re-renders is state placement. If state lives high in the tree but only a small subtree needs it, every update triggers a wide re-render. Push state down to the components that actually use it.
Context is another culprit. Every consumer re-renders when any context value changes. Split large context objects into smaller ones, or use a selector pattern with Zustand or Redux Toolkit to subscribe only to the slice you need.
Open the Profiler tab, hit Record, interact with your app, then stop. You get a flame graph showing every component render, its duration, and why it re-rendered. Look for components rendering more often than expected — especially those without a clear "why did this render?" reason.
Performance work is iterative. Start with bundle analysis (next/bundle-analyzer or vite-bundle-visualizer), profile the runtime with React DevTools, then apply targeted fixes. Don't pre-optimise. Measure, fix, measure again.