# Web Performance Optimisation Techniques II: Code Splitting

Code splitting is a technique where an application’s code is divided into smaller chunks or bundles, which are loaded **on demand**. Instead of loading the entire application at once, only the code needed for the current view is loaded. This improves performance and user experience by reducing the initial load time and optimising resource usage.

## **Why is Code Splitting Important?**

* **Faster Initial Load Times**: By loading only the code required for the initial page view, the application starts faster.
    
* **Enhanced User Experience**: Users spend less time waiting for the app to become interactive.
    
* **Efficient Resource Usage**: Prevents unnecessary code from being loaded, saving bandwidth and processing power.
    
* **Improved Caching**: Shared modules are cached and reused across different parts of the application, reducing the number of server requests.
    

## **Key Concepts for Code Splitting**

To understand how code splitting works, let’s first explore two key concepts: **dynamic imports** and **lazy loading**. These concepts work together to make code splitting possible and effective.

### **What are Dynamic Imports?**

Dynamic imports are a **JavaScript feature** that allows you to load modules (or chunks of code) **asynchronously at runtime**, rather than loading everything upfront. Instead of using a regular `import` statement (which loads all the code immediately), you use the `import()` function, which returns a **Promise**. This means the code is only loaded when it’s actually needed.

#### Example: Static Import vs. Dynamic Import

```javascript
// Static import (loads immediately)
import myModule from './myModule.js';

// Dynamic import (loads only when needed)
import('./myModule.js').then((module) => {
  module.default(); // Use the module after it's loaded
});
```

**In the example above:**

* The **static import** loads `myModule.js` as soon as the app starts.
    
* The **dynamic import** loads `myModule.js` only when the `import()` function is called.
    

This means the code inside `myModule.js` won’t be downloaded until it’s actually required, which is the foundation of **code splitting**.

### **What is Lazy Loading?**

Lazy loading is a **design pattern** that delays the loading of non-essential resources (like JavaScript modules, components, or images) until they are actually needed. This helps improve performance by reducing the amount of code loaded upfront.

#### How Does Lazy Loading Work?

* Lazy loading of **javascript modules** is typically implemented using **dynamic imports** (`import()`). For example, instead of loading a component immediately, you load it only when the user interacts with a specific feature or navigates to a specific route.
    
* **Also,** different frameworks provide built-in ways to implement lazy loading. For instance **React** uses `React.lazy` and **Vue** uses `defineAsyncComponent` to lazy-load components.
    

## **Tools for Code Splitting**

Code Splitting is implemented using a combination of **framework-specific tools** and **general-purpose build tools**. These tools work hand in hand to achieve efficient code splitting:

* **Framework-Specific Tools**: These are built into frameworks like React, Vue, and Angular. They provide easy-to-use features to define split points and lazy-load components.
    
* **General-Purpose Build Tools**: They handle the actual splitting of code into smaller chunks and optimise the bundles for production.
    

### **Framework Specific Tools**

| **Tool** | **Framework** | **Key Feature** |
| --- | --- | --- |
| [**React.lazy**](https://react.dev/reference/react/lazy) | React | Built-in React feature for lazy-loading components with `Suspense`. |
| [**Next.js**](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#1-code-splitting) | React | Automatic route-based splitting and dynamic imports. |
| [**defineAsyncComponent**](https://vuejs.org/api/general.html#defineasynccomponent) | Vue | Vue’s feature for lazy-loading components using dynamic imports. |
| [**Angular Router**](https://angular.dev/guide/routing/common-router-tasks#lazy-loading) | Angular | Router-based lazy loading with `loadChildren`. |
| [**Vue Router**](https://router.vuejs.org/guide/advanced/lazy-loading.html#Lazy-Loading-Routes) | Vue.js | Lazy-loaded routes using dynamic imports. |

### **General Purpose Build Tools**

| **Tool** | **Key Feature** |
| --- | --- |
| [**Webpack**](https://webpack.js.org/guides/code-splitting/) | Built-in support for dynamic imports and shared dependency optimization. |
| [**Vite**](https://vite.dev/guide/features.html#css-code-splitting) | Native ESM support for fast builds and automatic code splitting. |
| [**Rollup**](https://rollupjs.org/tutorial/#code-splitting) | Supports dynamic imports and manual chunking. |
| [**Parcel**](https://parceljs.org/recipes/react/#code-splitting) | Zero-configuration code splitting with dynamic imports. |
| [**Snowpack**](https://www.snowpack.dev/guides/optimize-and-bundle#nav-primary) | Native ESM support for fast builds and code splitting. |
| [Rsbuild](https://rsbuild.dev/guide/optimization/code-splitting) | High-performance Rust-powered build system with efficient code splitting. |

## **Practical Implementation of Code Splitting**

Now, let’s explore how code splitting works under the hood using a simple React app with the following files:

* **App.js (Main entry point)**
    

```javascript
import React from 'react';
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';
import HomePage from './HomePage';
import Dashboard from './Dashboard';

const App = () => {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link> | <Link to="/dashboard">Dashboard</Link>
      </nav>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Router>
  );
};

export default App;
```

* **HomePage.js**
    

```javascript
import React from 'react';

const HomePage = () => {
  return <div>Welcome to the Home Page!</div>;
};

export default HomePage;
```

* **Dashboard.js**
    

```javascript
import React from 'react';

const Dashboard = () => {
  return <div>Welcome to the Dashboard!</div>;
};

export default Dashboard;
```

**In this version:**

* **Initial Load:** The browser downloads the entire JavaScript bundle (e.g., `main.js`), which includes `App.js`, `HomePage.js`, and `Dashboard.js`.
    
* **No On-Demand Loading**: Since all components are bundled together, the app loads everything upfront, which can lead to slower initial load times especially for larger applications.
    

### **Adding Code Splitting to the App**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741482637474/9048e662-a8a7-484d-a545-5b2ac1deb6d0.png align="center")

### Step 1: Specifying Split Points

The developer defines **split points** in the code using the framework-specific tool.

**Let’s modify** `App.js` **to use** `React.lazy` **and** `Suspense` **for lazy loading:**

```javascript
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';

// Lazy-load the components
const HomePage = lazy(() => import('./HomePage'));
const Dashboard = lazy(() => import('./Dashboard'));

const App = () => {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link> | <Link to="/dashboard">Dashboard</Link>
      </nav>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </Router>
  );
};

export default App;
```

#### What’s Happening Here?

* We have specified that `HomePage` and `Dashboard` should be loaded **on demand** using lazy loading.
    
* `<Suspense fallback={<div>Loading...</div>}>` provides a fallback UI while the components are being loaded.
    

### Step 2: Build Tool Analyses the Code

Once the developer specifies the split points, the **build tool** (e.g., Webpack, Vite) analyses the application’s codebase to:

* Identify dependencies and entry points.
    
* Create a **dependency graph**, which maps out how all the modules (files) in the application are connected.
    

**For the example above,** the dependency graph might look like this:

```plaintext
App.js
├── HomePage.js
└── Dashboard.js
```

**This graph tells the tool that:**

* `App.js` depends on `HomePage.js` and `Dashboard.js`.
    
* `HomePage.js` and `Dashboard.js` are independent of each other.
    

### Step 3: Build Tool Splits the Code

Based on the dependency graph and split points, the build tool divides the code into smaller chunks. The splitting is done based on:

* **Dynamic Imports**: Code that is loaded only when needed (e.g., `import('./HomePage')`).
    
* **Entry Points**: Different parts of the application (e.g., homepage, dashboard) can be split into separate bundles.
    
* **Shared Dependencies**: Common modules (e.g., libraries like `React`) are split into shared bundles to avoid duplication.
    

### Step 4: Build Tool Generates Bundles

The build tool generates multiple smaller bundles instead of one large bundle. Here’s what the output might look like after code splitting:

```plaintext
dist/
├── main.js
├── HomePage.chunk.js
└── Dashboard.chunk.js
```

* **main.js** (Initial bundle containing `App.js` and shared dependencies like `React`).
    
* **HomePage.chunk.js** (Chunk for the `HomePage` component).
    
* **Dashboard.chunk.js** (Chunk for the `Dashboard` component).
    

### Step 5: Loading Bundles in the Browser

When the application runs in the browser, the **code splitting technique** comes into play:

1. **Initial Load**:
    
    * The browser loads `main.js`, which contains the core application logic and the `Suspense` fallback UI.
        
    * The code for `HomePage` and `Dashboard` is **not** included in the initial bundle.
        
2. **On-Demand Loading**:
    
    * When the user navigates to the home page, the browser dynamically fetches `HomePage.chunk.js` and renders the `HomePage` component.
        
    * When the user navigates to the dashboard, the browser dynamically fetches `Dashboard.chunk.js` and renders the `Dashboard` component.
        

### Note: Caching and Reusing Bundles

* **Shared Dependencies**: If multiple chunks depend on the same module (e.g., `React`), the build tool ensures that the module is only included once in a shared bundle. This reduces duplication and improves caching.
    
* **Browser Caching**: Once a chunk is loaded, it is cached by the browser. If the user revisits the same part of the application, the chunk is loaded from the cache instead of being downloaded again.
    

## **Conclusion**

Code splitting is a powerful technique to optimise web applications by loading only the necessary code upfront and fetching additional chunks on demand. By combining **dynamic imports**, **lazy loading**, and the right tools (both **framework-specific** and **general-purpose build tools**), developers can significantly improve performance, reduce load times, and enhance user experience.
