React Performance Optimization Checklist
Results you can expect: Reduce bundle size by 40%, improve initial page load by 60%, eliminate memory leaks, and create lightning-fast user experiences.
1. Performance Measurement & Audit
✅ Set up React DevTools Profiler
Before optimizing, measure. Install React DevTools and enable the Profiler to identify actual bottlenecks.
// Add this to your development environment
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}
✅ Track Core Web Vitals
Monitor LCP, FID, and CLS to understand real user performance impact.
// Custom hook for performance monitoring
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function useWebVitals() {
useEffect(() => {
getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);
}, []);
}
✅ Analyze bundle size with Webpack Bundle Analyzer
Visualize what's making your bundle large.
npm install --save-dev webpack-bundle-analyzer
npx webpack-bundle-analyzer build/static/js/*.js
2. Bundle Size Optimization
✅ Implement route-based code splitting
Split your app by routes to load only necessary code.
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// Lazy load route components
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
);
}
✅ Split large components
Break down heavy components into smaller, loadable chunks.
// Instead of importing heavy components directly
const DataVisualization = lazy(() => import('./DataVisualization'));
const ChartLibrary = lazy(() => import('./ChartLibrary'));
function Dashboard() {
const [showCharts, setShowCharts] = useState(false);
return (
<div>
<h1>Dashboard</h1>
{showCharts && (
<Suspense fallback={<div>Loading charts...</div>}>
<DataVisualization />
<ChartLibrary />
</Suspense>
)}
</div>
);
}
✅ Optimize imports for tree shaking
Import only what you need from large libraries.
// ❌ Bad - imports entire library
import * as _ from 'lodash';
import { Button, TextField, Dialog } from '@mui/material';
// ✅ Good - imports only needed functions
import debounce from 'lodash/debounce';
import groupBy from 'lodash/groupBy';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
3. Component Rendering Optimization
✅ Use React.memo for pure components
Prevent unnecessary re-renders of components with stable props.
// ❌ Re-renders on every parent update
function ExpensiveComponent({ data, onUpdate }) {
const processedData = expensiveCalculation(data);
return <div>{processedData}</div>;
}
// ✅ Only re-renders when props change
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
const processedData = expensiveCalculation(data);
return <div>{processedData}</div>;
}, (prevProps, nextProps) => {
// Custom comparison function
return prevProps.data.id === nextProps.data.id;
});
✅ Optimize expensive calculations with useMemo
Cache calculation results between renders.
function ProductList({ products, filters }) {
// ✅ Memoized calculation
const filteredProducts = useMemo(() => {
return products
.filter(product => matchesFilters(product, filters))
.sort((a, b) => a.price - b.price);
}, [products, filters]);
return (
<div>
{filteredProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
✅ Stabilize function references with useCallback
Prevent child re-renders caused by new function references.
function TodoApp() {
const [todos, setTodos] = useState([]);
// ✅ Stable function reference
const handleAddTodo = useCallback((text) => {
setTodos(prev => [...prev, { id: Date.now(), text }]);
}, []);
const handleToggleTodo = useCallback((id) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
}, []);
return (
<div>
<AddTodoForm onAdd={handleAddTodo} />
<TodoList todos={todos} onToggle={handleToggleTodo} />
</div>
);
}
4. Advanced Optimization Patterns
✅ Implement virtual scrolling for large lists
Handle thousands of items efficiently.
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
<ItemComponent item={items[index]} />
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</List>
);
}
✅ Optimize Context usage
Split contexts and memoize values to prevent cascade re-renders.
// ✅ Split into focused contexts
const UserContext = createContext();
const ThemeContext = createContext();
const DataContext = createContext();
function UserProvider({ children }) {
const [user, setUser] = useState(null);
// Memoize context value to prevent unnecessary re-renders
const value = useMemo(() => ({
user,
setUser,
}), [user]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
}
✅ Debounce expensive operations
Reduce frequency of expensive operations like API calls.
import { debounce } from 'lodash';
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
// Debounced search function
const debouncedSearch = useCallback(
debounce(async (searchQuery) => {
const results = await searchAPI(searchQuery);
setResults(results);
}, 300),
[]
);
useEffect(() => {
if (query.length > 2) {
debouncedSearch(query);
}
}, [query, debouncedSearch]);
return (
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search products..."
/>
);
}
5. State Management Performance
✅ Normalize complex state shapes
Avoid deeply nested updates that cause wide re-renders.
// ✅ Normalized state structure
const [users, setUsers] = useState({
1: { id: 1, name: 'John', postIds: [1] }
});
const [posts, setPosts] = useState({
1: { id: 1, title: 'Post 1', userId: 1 }
});
✅ Use reducer for complex state logic
Centralize state updates and optimize with immer.
import { useImmerReducer } from 'use-immer';
function todosReducer(draft, action) {
switch (action.type) {
case 'ADD_TODO':
draft.push({ id: Date.now(), text: action.text, completed: false });
break;
case 'TOGGLE_TODO':
const todo = draft.find(todo => todo.id === action.id);
if (todo) todo.completed = !todo.completed;
break;
default:
break;
}
}
function TodoApp() {
const [todos, dispatch] = useImmerReducer(todosReducer, []);
// Component implementation...
}
6. Network & Data Fetching Optimization
✅ Implement request deduplication
Prevent duplicate API calls for the same data.
// Custom hook with request deduplication
const requestCache = new Map();
function useApiData(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
// Check if request is already in progress
if (requestCache.has(url)) {
requestCache.get(url).then(setData);
return;
}
setLoading(true);
const request = fetch(url).then(res => res.json());
requestCache.set(url, request);
request
.then(data => {
setData(data);
setLoading(false);
})
.catch(() => {
requestCache.delete(url);
setLoading(false);
});
// Clean up cache after some time
setTimeout(() => requestCache.delete(url), 30000);
}, [url]);
return { data, loading };
}
✅ Implement intelligent caching
Cache API responses with automatic invalidation.
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 3,
refetchOnWindowFocus: false,
},
},
});
function useProducts() {
return useQuery('products', fetchProducts, {
select: useCallback((data) => {
// Transform data to reduce re-renders
return data.map(product => ({
id: product.id,
name: product.name,
price: product.price,
}));
}, []),
});
}
Performance Monitoring & Alerts
✅ Set up automated performance monitoring
Track performance regressions in production.
// Performance monitoring utility
function trackPerformance(metricName, value) {
// Send to analytics service
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', 'timing_complete', {
name: metricName,
value: Math.round(value),
});
}
}
// Custom hook for performance tracking
function usePerformanceTracking(componentName) {
useEffect(() => {
const startTime = performance.now();
return () => {
const endTime = performance.now();
trackPerformance(`${componentName}_render_time`, endTime - startTime);
};
}, [componentName]);
}
Common Performance Anti-patterns to Avoid
❌ Don't memoize everything
Memoization has overhead. Only memoize expensive calculations or frequently changing props.
// ❌ Unnecessary memoization
const simpleValue = useMemo(() => props.name, [props.name]);
// ✅ Direct usage is more efficient
const simpleValue = props.name;
❌ Don't create objects in render
Object creation in render causes unnecessary re-renders.
// ❌ New object on every render
function MyComponent({ items }) {
return <List items={items} config={{ sortable: true, filterable: false }} />;
}
// ✅ Stable object reference
const LIST_CONFIG = { sortable: true, filterable: false };
function MyComponent({ items }) {
return <List items={items} config={LIST_CONFIG} />;
}
Results You Can Expect
By implementing these optimization techniques, you can expect:
- 40% reduction in bundle size through effective code splitting and tree shaking
- 60% improvement in First Contentful Paint with optimized component architecture
- Elimination of memory leaks that cause performance degradation
- 70% reduction in API calls through intelligent caching strategies
- Smooth 60fps animations even on low-end devices
- Improved Core Web Vitals scores leading to better SEO rankings
Next Steps
- Audit your current app using React DevTools Profiler
- Start with the biggest impact items - usually bundle size and expensive renders
- Measure before and after each optimization
- Set up monitoring to catch performance regressions
- Make performance a team priority by establishing performance budgets
Remember: Premature optimization is the root of all evil, but strategic optimization based on real data can dramatically improve user experience.
This guide is based on real-world experience optimizing React applications serving millions of users. For personalized optimization help, contact me directly.