Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Performance Optimization
React is fast by default, but complex applications can slow down. This chapter covers techniques to identify bottlenecks and optimize your React apps. A critical mindset shift: Optimization in React is not about making every component fast — it is about making slow components faster. The Virtual DOM already prevents unnecessary real DOM updates. Most performance problems come from components re-rendering too often or doing too much work during a render. The tools in this chapter help you identify and fix those specific bottlenecks.When to Optimize
Understanding Re-renders
Every time state or props change, React re-renders components. Understanding this is key to optimization.What Causes Re-renders?
- Component’s state changes (
useState,useReducer) - Parent component re-renders — this is the biggest surprise for beginners. When a parent re-renders, all of its children re-render by default, even if their props have not changed.
- Context value changes — every component that calls
useContextre-renders when the provider’s value changes. - Custom hook state changes — if a hook uses
useStateinternally, the component calling that hook re-renders when that state changes.
Important clarification: Re-rendering does not mean the real DOM is updated. React’s diffing algorithm compares the new Virtual DOM with the old one and only applies actual DOM changes for the differences. Re-renders are usually fast. The problem arises when a re-render triggers expensive computations (filtering large arrays, complex calculations) or when thousands of components re-render unnecessarily.
React DevTools Profiler
The Profiler helps identify what’s rendering and why.Using the Profiler
- Install React DevTools browser extension
- Open DevTools → Profiler tab
- Click “Record” and interact with your app
- Analyze the flame graph
What to Look For
- Frequent re-renders of the same component
- Slow components (long render times)
- Cascade re-renders (parent update causing all children to update)
React.memo - Prevent Unnecessary Re-renders
React.memo is a higher-order component that memoizes functional components.
Basic Usage
Custom Comparison
useMemo - Memoize Expensive Calculations
useMemo caches the result of expensive computations so they only run when their inputs change.
Analogy: useMemo is like a spreadsheet cell with a formula. The cell only recalculates when the cells it references change — not every time you look at the spreadsheet. useCallback works the same way but for functions instead of values — it is literally useMemo(() => fn, deps) under the hood.
When to Use useMemo
| Use useMemo | Don’t Use useMemo |
|---|---|
| Expensive calculations | Simple math/string operations |
| Large array operations | Small arrays (fewer than 100 items) |
| Object creation for memoized children | Objects used once |
| Derived data from props/state | Values passed to native elements |
useCallback - Memoize Functions
useCallback caches function references to prevent unnecessary re-renders of child components.
Code Splitting with React.lazy
Split your bundle so users only download what they need.Named Exports with Lazy
Virtualization for Long Lists
Only render visible items in long lists.Using react-window
Variable Size List
Debouncing and Throttling
Limit how often functions are called.Debouncing (Wait for pause)
Throttling (Limit frequency)
Image Optimization
Lazy Loading Images
Progressive Loading with Blur
Web Vitals
Key metrics for user experience:| Metric | Description | Target |
|---|---|---|
| LCP (Largest Contentful Paint) | Main content visible | < 2.5s |
| FID (First Input Delay) | Time to interactive | < 100ms |
| CLS (Cumulative Layout Shift) | Visual stability | < 0.1 |
| TTFB (Time to First Byte) | Server response time | < 600ms |
Measuring Web Vitals
Production Build Optimization
Vite Production Build
Analyze Bundle Size
Environment-Specific Code
Deployment
Vercel (Recommended for React)
Netlify
Createnetlify.toml:
Docker
Optimization Pitfalls
🎯 Practice Exercises
Exercise 1: Optimize a Slow List
Exercise 1: Optimize a Slow List
Summary
| Technique | Use Case |
|---|---|
| React.memo | Prevent child re-renders when props unchanged |
| useMemo | Cache expensive calculations |
| useCallback | Cache function references |
| React.lazy | Code split by route/component |
| Virtualization | Long lists (100+ items) |
| Debouncing | Search inputs, resize handlers |
| Lazy loading | Images below the fold |
| Bundle analysis | Identify large dependencies |
| Web Vitals | Measure real user experience |
Next Steps
In the next chapter, you’ll learn about Authentication & Protected Routes — securing your React applications!
Interview Deep-Dive
Explain the relationship between React.memo, useMemo, and useCallback. When do you need all three together, and when is each one alone insufficient?
Explain the relationship between React.memo, useMemo, and useCallback. When do you need all three together, and when is each one alone insufficient?
Strong Answer:
These three tools form an optimization chain, and using one without the others often provides zero benefit.
React.memo wraps a component and skips re-rendering when its props have not changed (by shallow comparison). But it only works if the props are actually the same by reference. If the parent passes a new object or function reference every render, React.memo compares the old and new references, finds them different, and re-renders anyway.useMemo fixes the object reference problem: const config = useMemo(() => ({ theme: 'dark' }), []) returns the same object reference across renders as long as dependencies are stable.useCallback fixes the function reference problem: const handleClick = useCallback(() => doSomething(id), [id]) returns the same function reference as long as id has not changed.When you need all three together: a parent renders a memoized child that receives both an object prop and a callback prop. Without useMemo on the object and useCallback on the callback, React.memo on the child is useless because it receives new references every render.When each alone is insufficient: React.memo alone fails when props contain unstable references. useMemo alone is pointless if the child component is not memoized. useCallback alone is pointless if the child is not wrapped in React.memo. The anti-pattern I see most often: developers wrap every function in useCallback “for performance” without memoizing the child component. The useCallback adds overhead with zero benefit.Follow-up: How do you measure whether memoization is actually helping? What tools do you use?React DevTools Profiler is the primary tool. Record an interaction, then examine the flame graph. Components highlighted in gray did not render (memoization worked). The “Why did this render?” feature tells you exactly which props changed, confirming whether your memoization is preventing re-renders.For precise measurement, use the React.Profiler component with an onRender callback that logs actualDuration. Compare before and after memoization across multiple interactions.The rule: if you cannot measure a difference, do not memoize. The overhead of useMemo and useCallback (dependency comparison, closure storage) is small but nonzero. For trivial computations, memoization is slower than recomputing.You are profiling a React app and discover that a list of 500 items re-renders entirely when a single item changes. Walk through your optimization strategy.
You are profiling a React app and discover that a list of 500 items re-renders entirely when a single item changes. Walk through your optimization strategy.
Strong Answer:
Step 1: Identify why every item re-renders. Open React DevTools Profiler, record the interaction, and check “Why did this render?” for the list items. Common causes: the parent re-renders and children are not memoized, or a new callback reference is passed as a prop on every render.Step 2: Extract the list item into its own component if it is not already. You cannot memoize inline JSX in a map callback.Step 3: Wrap the list item component in
React.memo. This is the biggest single win — now each item only re-renders when its own props change.Step 4: Stabilize callback props. If each item receives onClick={() => handleSelect(item.id)}, that arrow function is new on every render, defeating React.memo. Refactor so the item receives a stable onSelect via useCallback and the item ID as a separate prop, letting the item call onSelect(id) internally.Step 5: If the list data is derived (filtered, sorted), wrap the derivation in useMemo so the filtered array reference stays stable when inputs have not changed.Step 6: For very long lists (1000+ items), add virtualization with react-window or react-virtuoso. This caps rendered components at the visible window.Step 7: Verify with the Profiler that only the changed item re-renders. The flame graph should show 499 gray items and 1 highlighted item.Follow-up: React.memo uses shallow comparison. What happens when list items receive nested objects as props?Shallow comparison checks reference equality for each prop. If a prop is a nested object recreated every render, React.memo sees a new reference and re-renders even if all values are identical.Three solutions: First, stabilize the object with useMemo in the parent. Second, flatten the props — pass userName and userCity as separate primitive props instead of a nested object. Primitives are compared by value. Third, provide a custom comparison function to React.memo that checks the relevant fields. The custom comparator is powerful but dangerous if you forget to compare a prop that affects rendering.What are React Server Components and how do they change the performance optimization model?
What are React Server Components and how do they change the performance optimization model?
Strong Answer:
React Server Components (RSC) execute on the server and send their rendered output to the client as a serialized component tree. They never run in the browser — their JavaScript is not included in the client bundle. This fundamentally changes the optimization model because the primary concern shifts from “minimize client-side re-renders” to “minimize what ships to the client.”In the traditional SPA model, every component’s code is bundled, downloaded, parsed, and executed in the browser. Optimization means reducing re-renders (memo, useMemo, useCallback) and reducing bundle size (code splitting, tree shaking).With RSC, a component that fetches data, processes it, and renders HTML can run entirely on the server. The client receives the rendered output with no JavaScript for that component. For a page that imports a 200KB markdown parser to render blog content, RSC eliminates that 200KB from the client bundle entirely.The new optimization model: make as many components as possible Server Components (the default in Next.js App Router). Only add “use client” when you need interactivity (state, effects, event handlers, browser APIs). The boundary between server and client components is where you optimize.The tradeoff: Server Components cannot use hooks, cannot manage state, and cannot respond to user events. They are purely for rendering. This forces a clean separation between data fetching (server) and interactivity (client).Follow-up: How does streaming with Suspense boundaries improve perceived performance with Server Components?Without streaming, the server processes the entire page, then sends the complete HTML in one response. The user sees a blank page until the slowest data fetch completes. With streaming, the server sends HTML for fast-to-render parts immediately and streams in slower parts as they become ready.Suspense boundaries mark the streaming cut points. A Server Component wrapped in Suspense sends the skeleton HTML immediately and streams the real content when the data fetch resolves. The browser progressively replaces skeletons with content without any JavaScript.For the user, a page that takes 3 seconds to fully load feels near-instant because the layout, navigation, and above-the-fold content appear in 200ms. Slower sections stream in later. Client Components within the stream are hydrated selectively as their code loads, so interactivity arrives progressively too.