Performance
Lazy Loading
Master React lazy loading, covering React.lazy components, dynamic imports, viewport-triggered loading, and initial bundle optimizations.
1. Learning Objectives
In this lesson, you will learn how to optimize page load speeds by loading code on demand. By the end of this topic, you will be able to:
- Differentiate eager loading from lazy loading.
- Import components dynamically using the
React.lazyAPI. - Configure dynamic imports using the
import()function. - Defer rendering of off-screen components using viewport triggers (Intersection Observer).
- Reduce initial application bundle sizes to improve load performance.
2. Overview
By default, when a user visits a React website, the browser downloads the entire application bundle before rendering anything. **Lazy Loading** is an optimization technique that splits this bundle into smaller chunks, downloading and rendering specific components (like heavy admin panels or modals) only when they are needed.
3. Why This Topic Matters
Lazy loading is key to building fast, high-performance web applications:
- Faster Time to Interactive (TTI): Reducing the size of the initial JavaScript bundle allows the browser to download, parse, and render the page much faster.
- Bandwidth Savings: Users only download the code for the sections of the site they actually visit, saving mobile data.
4. Real-World Analogy
Think of lazy loading like **a chef assembling ingredients for a meal**:
- Eager Loading: Chopping and preparing every single ingredient in the pantry (pizza dough, pasta, steak, soup, dessert) before the customer even orders. This wastes space and delays service.
- Lazy Loading: Preparing only the pizza dough initially. When the customer orders dessert (requests the component), you get the dessert ingredients out of the freezer and assemble it. You only prepare ingredients when they are ordered, saving kitchen resources.
5. Core Concepts
Below is a comparison of Eager and Lazy loading patterns:
| Property | Eager Loading Pattern (Default) | Lazy Loading Pattern (Optimized) |
|---|---|---|
| Initial Download Size | High. Downloads the entire site's code at once. | Low. Downloads only the code needed for the landing page. |
| Page Transitions | Instant (since the code is already loaded). | Requires a brief loading pause as new chunks download. |
| Implementation | Standard static import statements (e.g. import X from 'x'). |
Dynamic imports paired with React.lazy and Suspense. |
6. Syntax & API Reference
This example shows how to configure a component for lazy loading using `React.lazy`:
7. Visual Diagram
This diagram displays how bundlers split files and download them dynamically in the browser:
8. Live Example — Full Working Code
Below is a complete HTML page demonstrating lazy loading using a simulated dynamic import:
What just happened? Clicking "Load Heavy Component" triggers the rendering of LazyHeavyComponent. Because it is wrapped in `lazy` and `Suspense`, React displays the "Downloading javascript chunk..." fallback UI while downloading the code in the background. Once the promise resolves, it renders the loaded component.
9. Interactive Playground
Try It Yourself Challenges:
- Change the timeout delay in
delayImportto 3 seconds to test how the fallback loading indicator behaves on slow networks. - Add an "Unmount" button to reset the state, and verify how the component behaves when loaded a second time (hint: the chunk is already cached by the browser).
10. Common Mistakes
| Mistake | Why it happens | Wrong | Correct |
|---|---|---|---|
| Omitting the Suspense wrapper for lazy components | If you render a lazy component without wrapping it in a <Suspense> element, React will crash and throw a rendering error when the component attempts to mount. |
<LazyWidget /> (missing Suspense parent) |
<Suspense fallback=<Spinner />> |
| Importing lazy components inside render loops or components | Calling React.lazy inside a component block redeclares the lazy import on every render cycle. This loses the cached bundle and causes the component to reset on every parent render. |
function App() { |
Always declare React.lazy imports outside of component blocks, near the top of the file. |
11. Best Practices
- Declare lazy imports globally: Always call
React.lazyoutside of component rendering blocks. - Use default exports: Components imported using
React.lazymust be exported as **default exports**. - Optimize loading states: Choose lightweight components or skeleton frames for your `Suspense` fallbacks to keep the loading experience smooth.
- Integrate with Routing: Use lazy loading on route components (like `/admin` or `/dashboard`) to split code naturally.
12. Browser Compatibility/Requirements
`React.lazy` and dynamic imports require browsers that support ES modules (supported in all modern browsers).
13. Interview Questions
Q1: How do you implement lazy loading for components in React? Discuss the roles of `React.lazy` and ``.
Answer: To implement lazy loading: (1) declare the component using `React.lazy` and a dynamic import statement (e.g. `const Lazy = lazy(() => import('./Comp'))`). (2) Wrap the lazy component in a `` wrapper when rendering. The `` component handles the pending state by rendering the `fallback` UI while the javascript bundle downloads, swapping in the actual component once the download completes.
Q2: Why must `React.lazy` components be exported as default exports? How can you lazy load named exports?
Answer: `React.lazy` expects a promise that resolves to a module object with a `.default` property containing the React component. To lazy load a named export, you must resolve the promise with an object containing the named export mapped to the default key: `lazy(() => import('./Module').then(module => ({ default: module.NamedExport })))`
14. Debugging Exercise
Identify why this router lazy-loading setup crashes the browser when visiting '/admin':
Diagnosis: The lazy-loaded AdminPanel component is not wrapped in a Suspense component. When React matches the route and tries to mount the lazy component, it crashes because there is no fallback loading UI declared.
Fix: Wrap the lazy component or the entire routing tree in a Suspense wrapper:
15. Practice Exercises
Exercise 1: Viewport lazy-loaded card details
Build a component named LazyCard. Using a scroll box or the Intersection Observer API, trigger the dynamic loading of a heavy details sub-component only when the card intersects with the user's viewport.
16. Scenario-Based Challenge
The Multi-Tenant Core Admin Panel Bundler Splitting:
You are designing a SaaS platform where different companies use different widgets. The main page bundle is getting too large. Explain how you would structure the dashboard using React.lazy and dynamic imports to ensure users only download the code for the widgets enabled for their specific company.
17. Quick Quiz
Q1: Which method is called to dynamically import a JavaScript module in modern JS?
A) require()
B) import()
C) fetchModule()
Answer: B — The dynamic import() function returns a promise that resolves to the module namespace object, allowing on-demand code loading.
18. Summary & Key Takeaways
- Lazy loading splits your code bundle to improve the page load performance.
- Import components dynamically using the
React.lazy()API. - Always wrap lazy components in a
Suspensecomponent with a fallback UI. - Declare
React.lazyimports outside of component blocks. - Lazy loaded components must be exported as default exports.
19. Cheat Sheet
| Lazy API Syntax | Description |
|---|---|
const LC = lazy(() => import('./C')); |
Imports a component dynamically for lazy loading (requires default export). |
<Suspense fallback={<Loader />}> |
Wraps lazy components and displays a fallback UI during download. |
() => import('./M').then(m => ({ default: m.N })) |
Helper syntax to lazy load a named export. |