Performance Optimization
Optimizing React apps — memoization, lazy loading, and profiling
What you'll learn
Diagnose performance bottlenecks using Lighthouse and Chrome DevTools
Optimize Core Web Vitals: LCP, FID/INP, and CLS
Implement code splitting and lazy loading to reduce bundle size
Use proper image formats (AVIF, WebP) and responsive images
Apply caching strategies (Cache-Control, ETag, Service Worker) for repeat visits
Measure and monitor real-user performance with RUM data
Performance is a feature. Optimize judiciously.
React.memo
import { memo } from "react"
const ExpensiveList = memo(function ExpensiveList({
items,
}: {
items: string[]
}) {
return (
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
)
})
useMemo and useCallback
import { useMemo, useCallback } from "react"
// Memoize computed values
const sorted = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name))
}, [items])
// Memoize callbacks
const handleClick = useCallback((id: string) => {
setSelected(id)
}, [])
Code Splitting with Next.js
import dynamic from "next/dynamic"
const HeavyComponent = dynamic(() => import("@/components/Heavy"), {
loading: () => <p>Loading...</p>,
})
Best Practices
- Measure before optimizing (React Profiler, Lighthouse)
- Lazy load below-the-fold content
- Avoid unnecessary re-renders
- Use proper key props in lists
- Optimize images (Next.js Image component)
Next Steps
Key Takeaways
Core Web Vitals (LCP, FID/INP, CLS) are the primary performance metrics Google uses in ranking
Image optimization (AVIF/WebP, responsive sizes, lazy loading) is the single highest-impact perf improvement
Code splitting via dynamic import() reduces initial bundle size by loading code on demand
Caching strategies (Cache-Control, ETags, Service Worker) eliminate redundant network requests
Real User Monitoring (RUM) captures actual user experience data that synthetic tests miss
Measure before optimizing — the 90/10 rule applies: 90% of gains come from fixing 10% of issues