React.memo & Component Memoization
React.memo prevents a component from re-rendering when its props haven't changed. Use it for expensive components that receive the same props frequently.
import React, { useState, memo, useCallback } from 'react';
// Expensive list item - only re-render when 'item' prop changes
const ListItem = memo(function ListItem({
item,
onDelete,
}: {
item: { id: number; text: string };
onDelete: (id: number) => void;
}) {
console.log(`Rendering: ${item.text}`);
return (
<li className="flex justify-between items-center p-3">
<span>{item.text}</span>
<button onClick={() => onDelete(item.id)}>Delete</button>
</li>
);
});
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React" },
{ id: 2, text: "Build something" },
]);
const [input, setInput] = useState('');
// useCallback ensures same function reference for memo to work
const handleDelete = useCallback((id: number) => {
setTodos(prev => prev.filter(t => t.id !== id));
}, []);
return (
<div>
<ul>
{todos.map(todo => (
<ListItem key={todo.id} item={todo} onDelete={handleDelete} />
))}
</ul>
<input value={input} onChange={e => setInput(e.target.value)} />
</div>
);
}
Lazy Loading & Code Splitting
Split your bundle into smaller chunks loaded on demand. This reduces initial page load time dramatically.
import { lazy, Suspense } from 'react';
import dynamic from 'next/dynamic';
// React.lazy for client-side code splitting
const HeavyChart = lazy(() => import('./HeavyChart'));
const MarkdownEditor = lazy(() => import('./MarkdownEditor'));
// Next.js dynamic import with options
const VideoPlayer = dynamic(() => import('./VideoPlayer'), {
loading: () => <div className="animate-pulse bg-gray-800 h-64 rounded-xl" />,
ssr: false, // Don't render on server (browser-only component)
});
function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<button onClick={() => setShowChart(true)}>Load Chart</button>
{showChart && (
// Suspense provides loading UI while lazy component loads
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart data={[1, 2, 3]} />
</Suspense>
)}
{/* VideoPlayer loaded client-side only */}
<VideoPlayer src="/video.mp4" />
</div>
);
}
Profiling & Measuring Performance
Before optimizing, measure. Use React DevTools Profiler and the built-in Profiler component to identify bottlenecks.
import { Profiler, ProfilerOnRenderCallback } from 'react';
const onRender: ProfilerOnRenderCallback = (
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) => {
// Log slow renders (>16ms = below 60fps)
if (actualDuration > 16) {
console.warn(`Slow render in "${id}": ${actualDuration.toFixed(2)}ms`);
}
};
function App() {
return (
<Profiler id="MainContent" onRender={onRender}>
<MainContent />
</Profiler>
);
}
// Use Web Vitals for real-world metrics
export function reportWebVitals(metric: NextWebVitalsMetric) {
switch (metric.name) {
case 'LCP': // Largest Contentful Paint
case 'FCP': // First Contentful Paint
case 'CLS': // Cumulative Layout Shift
case 'FID': // First Input Delay
case 'TTFB': // Time To First Byte
console.log(metric);
break;
}
}
Target: LCP < 2.5s, FID < 100ms, CLS < 0.1. These are Google's Core Web Vitals thresholds for "Good" performance.