Performance
Code Splitting
Master React code splitting, covering dynamic imports, route-based bundle splitting, bundle analysis, and performance optimization.
1. Learning Objectives
In this lesson, you will learn how to optimize bundle delivery using code splitting. By the end of this topic, you will be able to:
- Describe how web bundlers compile and optimize Javascript assets.
- Implement route-based code splitting inside routing trees.
- Differentiate route-based splitting from component-based dynamic loading.
- Analyze bundle size maps using visualization tools (Rollup/Webpack).
- Configure vendor chunk splits to improve browser caching.
2. Overview
Modern React applications rely on bundlers (like Vite, Webpack, or Rollup) to merge all code files into a single, large JavaScript bundle. **Code Splitting** tells the bundler to split this large file into multiple smaller chunks that can be downloaded dynamically on demand, speeding up initial page loads.
3. Why This Topic Matters
Managing bundle sizes is critical for keeping load times fast as applications grow:
- Monolithic Bundles: As you add features and third-party libraries, your single bundle size increases. This causes slow initial page loads, especially on mobile devices or slow networks.
- Optimal Caching: Splitting third-party libraries (like Lodash or Chart.js) into separate vendor chunks allows the browser to cache them permanently, since library code changes less frequently than application code.
4. Real-World Analogy
Think of code splitting like **loading a moving truck**:
- Monolithic Bundling: Packing every single belonging—including seasonal gear, winter clothes, and guest beds—into a single massive truck. You must wait for the entire truck to be packed and delivered before you can unpack a single toothbrush.
- Code Splitting: Packing essential items (toothbrush, bed sheets) into your car so you can settle in immediately (fast initial render). The guest beds and seasonal items are packed in secondary containers and shipped later, arriving only when they are needed.
5. Core Concepts
Below is a comparison of Route-based and Component-based code splitting:
| Property | Route-based Code Splitting | Component-based Code Splitting |
|---|---|---|
| Split Target | Applied to route page entry points (e.g. /admin page). |
Applied to specific heavy widgets (e.g. modals, text editors). |
| Chunk Download Trigger | Triggered automatically when the URL path changes. | Triggered by user interactions (like clicking a button). |
| Use Cases | Default layout splitting for separate pages. | Deferring heavy libraries until explicitly used. |
6. Syntax & API Reference
This example shows how to configure route-based code splitting:
7. Visual Diagram
This diagram displays how bundlers output dynamic file chunks:
8. Live Example — Full Working Code
Below is a complete HTML page demonstrating simulated route-based code splitting and chunk loading indicators:
What just happened? Clicking "Admin" triggers the lazy loading of LazyAdmin. Because it is wrapped in `Suspense`, React displays the "Downloading page bundle chunk..." fallback UI while loading the code. Once the module resolves, it renders the admin panel page.
9. Interactive Playground
Try It Yourself Challenges:
- Change the timeout delay in the dynamic import simulation to 3 seconds to see how the loading state behaves.
- Modify the layout to add a dynamic import for a heavy library mock (e.g. importing utility formulas on demand).
10. Common Mistakes
| Mistake | Why it happens | Wrong | Correct |
|---|---|---|---|
| Over-splitting minor components | Every dynamic import compiles into a separate network request. Splitting small, simple components (under 5 KB) creates too many small requests, increasing HTTP overhead and slowing down the site. | Lazy loading small button icons or header text. | Only split large page components (routes) or heavy third-party libraries (e.g. charts, text editors). |
| Omitting chunk loading error handlers | If a user is browsing on a poor connection or the site is redeployed (meaning old chunk filenames no longer exist on the server), dynamic imports will fail. Without error boundaries, this crashes the app. | Rendering route-based split components without an ErrorBoundary. |
Wrap dynamic components in an ErrorBoundary that prompts the user to refresh their browser on chunk load failures. |
11. Best Practices
- Use Route-Based Splitting: Always start code splitting at the route level to divide the application into logical page chunks.
- Split Large Libraries: Use dynamic imports (`import('library')`) inside event handlers to load heavy libraries (like chart components or file exporters) only when the user clicks the action.
- Analyze Bundle Maps: Regularly analyze bundle maps using tools like `rollup-plugin-visualizer` (Vite) or `webpack-bundle-analyzer` to identify and optimize large chunks.
- Add chunk error recovery: Catch chunk load errors in your Error Boundary to prompt the user to reload the page, ensuring they load the latest assets.
12. Browser Compatibility/Requirements
Dynamic imports require browser support for ES modules.
13. Interview Questions
Q1: How do modern bundlers compile dynamic imports into separate chunks behind the scenes?
Answer: When a bundler (like Vite or Webpack) detects a dynamic `import('./MyComponent')` statement, it treats the module as a split boundary. Instead of compiling it into the main bundle, the bundler creates a separate JavaScript chunk file. At runtime, when the dynamic import runs, the browser requests this chunk file over HTTP, loading it into memory on demand.
Q2: How do you identify large modules in your bundle? Discuss rollup and webpack bundle analyzers.
Answer: You can identify large modules by running bundle analyzers (like `rollup-plugin-visualizer` in Vite or `webpack-bundle-analyzer` in Webpack). These plugins generate an interactive HTML treemap of your build outputs, visualizing files as blocks where block sizes are proportional to their raw or zipped byte weights. This makes it easy to spot heavy third-party libraries or duplicate packages.
14. Debugging Exercise
Identify why the third-party PDF editor library continues to compile into the main index bundle, slowing down the page load:
Diagnosis: The library is imported using a static import statement (import) at the top of the file. This tells the bundler to compile the library directly into the main entry bundle, even though it is only used inside the click handler.
Fix: Change the import to a dynamic import inside the click handler to load the library on demand:
15. Practice Exercises
Exercise 1: Configure route-based code splitting
Set up a routing tree containing /catalog and /checkout routes. Configure these pages to load using route-based code splitting with custom loading fallbacks.
16. Scenario-Based Challenge
The Multi-page Admin Analytics Optimization Project:
You are designing a portal that uses heavy drawing libraries on the `/admin/analytics` page. General users do not visit this page, but they still pay the download cost of these charting libraries. Describe how you would configure code splitting to split this route and its libraries into a separate chunk.
17. Quick Quiz
Q1: What does the dynamic import() function return in JavaScript?
A) The resolved module exports object directly.
B) A Promise that resolves to the module namespace object.
C) An HTML5 script tag element.
Answer: B — Dynamic imports return a Promise that resolves to the module namespace object, allowing asynchronous code loading.
18. Summary & Key Takeaways
- Code splitting divides monolithic javascript bundles into smaller chunks.
- Use dynamic imports (`import()`) to load modules on demand.
- Apply route-based splitting to decrease initial page load times.
- Use bundle analyzers to find and optimize large packages.
- Always handle chunk load errors in a boundary wrapper to prevent app crashes.
19. Cheat Sheet
| Bundler / Split Pattern | Description |
|---|---|
const Page = lazy(() => import('./P')); |
Imports page modules dynamically for route splitting. |
const lib = await import('lib'); |
Imports libraries on demand inside actions or functions. |
rollup-plugin-visualizer |
Rollup/Vite plugin used to analyze and visualize bundle sizes. |