Tips and patterns for keeping your React apps fast and maintainable
As your React app grows, performance bottlenecks and code complexity can sneak in. This post walks through battle-tested tips, tools, and patterns that will help you keep your large React apps fast, responsive, and developer-friendly.
Let’s get started
1. Use React.memo
Smartly
React.memo
prevents unnecessary re-renders of functional components by shallowly comparing props.
const ExpensiveComponent = React.memo(({ data }) => {
// Only re-renders if props change
return <div>{data.name}</div>;
});
Use when:
- The component renders frequently.
- Props rarely change.
- Re-rendering is costly.
2. Split Your Bundle with React.lazy
Code-splitting helps you load parts of your app only when needed, keeping the initial load fast.
import React, { Suspense } from 'react';
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
🔍 Works great with route-based splitting via React Router or Next.js dynamic imports.
3. Avoid Anonymous Functions in JSX
Inline arrow functions create new references on every render, causing unnecessary re-renders.
Avoid:
<button onClick={() => doSomething(id)}>Click</button>
Instead:
const handleClick = useCallback(() => doSomething(id), [id]);
<button onClick={handleClick}>Click</button>
4. Normalize Your State Shape
In large apps, deeply nested state structures lead to unnecessary updates. Flatten and normalize your state for performance.
Use libraries like:
-
zustand
orjotai
for simplified state management -
redux-toolkit
withcreateEntityAdapter
for normalized lists
5. Clean Up with useEffect
Avoid memory leaks and stale references by always cleaning up effects:
useEffect(() => {
const id = setInterval(doSomething, 1000);
return () => clearInterval(id); // clean up
}, []);
6. Measure First, Optimize Later
Use tools like:
- React DevTools Profiler
- Chrome Lighthouse
- why-did-you-render
Install why-did-you-render
for development:
npm install @welldone-software/why-did-you-render
import React from 'react';
import whyDidYouRender from '@welldone-software/why-did-you-render';
whyDidYouRender(React, { trackAllPureComponents: true });
7. Use Production-Optimized Builds
Always check:
- Are you using
NODE_ENV=production
? - Are unused imports being tree-shaken?
For Next.js: it does this automatically.
For Create React App: run npm run build
.
8. Consider Virtualization for Large Lists
For huge lists (e.g. 1,000+ items), use:
import { FixedSizeList as List } from 'react-window';
<List height={400} itemCount={1000} itemSize={35} width={300}>
{({ index, style }) => (
<div style={style}>Row {index}</div>
)}
</List>
Final Thoughts
Performance isn't just about speed — it’s also about maintainability, scalability, and developer happiness.
✅ Measure before optimizing
✅ Use tools, not hacks
✅ Think long-term
Recommended Reads
- React Docs: Optimizing Performance
- Kent C. Dodds: Lazy loading & code splitting
- React Profiler Guide
Did you enjoy this post?
Let me know what tools or techniques you use to optimize your React apps!
Top comments (0)