Dwarves
Memo
Type ESC to close search bar

Code splitting in React

Code splitting is a technique used to optimize JavaScript bundles by breaking them into smaller chunks, loading only the necessary parts when they’re needed. This reduces the initial loading time for users, as they only download the essential code to render the initial view. Code splitting is particularly valuable in large applications where bundling everything together can lead to slow load times and performance issues.

We will explore various code splitting techniques, including their use cases and practical implementation examples.

Code splitting techniques

Entry point splitting

Entry point splitting involves separating the main application entry points. In Webpack, you can specify multiple entry points, each generating a separate bundle. This technique is helpful in multi-page applications (MPAs) or if you have clearly separate sections within a single-page app (SPA) that can load independently.

// webpack.config.js
module.exports = {
  entry: {
    home: './src/home.js',
    dashboard: './src/dashboard.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: __dirname + '/dist',
  },
}

Here, Webpack creates home.bundle.js and dashboard.bundle.js, loading only the necessary code when the user navigates to either the home page or dashboard.

Use case:

Route-based code splitting

Route-based code splitting is common in SPAs. Instead of loading the entire app at once, only the components needed for the current route are loaded initially. Additional routes are loaded only when the user navigates to them.

Example with React Router and React.lazy:

import React, { lazy, Suspense } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'

const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
const Contact = lazy(() => import('./Contact'))

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Switch>
      </Suspense>
    </Router>
  )
}

Explanation:

Benefits:

Component-level code splitting with React.lazy and Suspense

If you have a large component that doesn’t need to load right away (e.g., a modal or sidebar), you can split it out and load it only when it’s needed. This helps reduce the initial bundle size, as non-essential components load asynchronously.

Example: Lazy Loading a Component

import React, { lazy, Suspense, useState } from 'react'

const UserProfile = lazy(() => import('./UserProfile'))

function App() {
  const [showProfile, setShowProfile] = useState(false)

  return (
    <div>
      <button onClick={() => setShowProfile((prev) => !prev)}>Toggle User Profile</button>
      <Suspense fallback={<div>Loading...</div>}>{showProfile && <UserProfile />}</Suspense>
    </div>
  )
}

Explanation:

Use cases:

Splitting large dependencies or utilities

Sometimes a single library or utility can significantly increase your bundle size. Instead of loading the entire library, use dynamic import() to load only the necessary part of the code when needed. This is particularly useful for utilities like date formatting or image processing libraries that may not be required on every page.

Example: Lazy Loading a utility library

function DateFormatter({ date }) {
  const [formattedDate, setFormattedDate] = useState('')

  useEffect(() => {
    async function loadDateLibrary() {
      const { format } = await import('date-fns')
      setFormattedDate(format(new Date(date), 'yyyy-MM-dd'))
    }
    loadDateLibrary()
  }, [date])

  return <div>{formattedDate}</div>
}

Explanation:

Benefits:

Library-Based Code Splitting with react-loadable

For more complex loading scenarios, react-loadable offers additional features such as delayed loading, error boundaries, and preloading. It’s especially helpful if you want to provide a custom loading experience or handle loading errors gracefully.

Example using react-loadable

import Loadable from 'react-loadable'

const LoadableComponent = Loadable({
  loader: () => import('./HeavyComponent'),
  loading: ({ isLoading, pastDelay, error }) => {
    if (isLoading && pastDelay) return <div>Loading...</div>
    if (error) return <div>Error loading component!</div>
    return null
  },
  delay: 300, // Shows loading only if loading takes longer than 300ms
})

function App() {
  return <LoadableComponent />
}

Explanation:

Use cases:

Advanced Code Splitting Techniques

Preloading and Prefetching Components

Preloading and prefetching are useful when you want to load components in advance, either to improve performance or to anticipate user interactions.

const UserProfile = lazy(() => import(/* webpackPrefetch: true */ './UserProfile'))

Use case:

Bundle Splitting

Bundling tools, such as Webpack, that have the SplitChunksPlugin component can be configured to automatically separate common dependencies (like react or lodash) into distinct bundles. This avoids redundant code in each chunk and reduces the total bundle size.

Example configuration in Webpack:

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000,
      maxSize: 50000,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
}

Explanation:

Use Case:

Lazy loading images and assets

For non-JavaScript assets like images and fonts, you can also improve performance by loading them only when they’re in the viewport.

Example: Lazy Loading Images with loading="lazy"

function ImageComponent() {
  return <img src="path/to/image.jpg" loading="lazy" alt="Lazy loaded image" />
}

Explanation:

Benefits:

Summary

TechniqueBest ForExamples
Entry Point SplittingMulti-page apps with separate entry pointsHome, Admin, Dashboard entry points
Route-Based SplittingSingle-page apps, lazy loading route componentsLazy loading routes with React Router
Component-Level SplittingLarge components like modals, settings panelsLazy loading non-essential components
Large Dependency SplittingLibraries used infrequentlyDate formatting utilities, large image processing libs
Library-Based SplittingComponents that need advanced loading/error handlingreact-loadable for complex loading states
Preloading and PrefetchingAnticipating user actions to improve UXPreloading next route or component
Bundle SplittingAvoiding redundancy by splitting common dependenciesSplitting vendors bundle
Lazy Loading ImagesReducing initial page weight for media-rich applicationsloading="lazy" attribute on images

Each of these techniques targets a specific aspect of load management and bundle optimization, providing flexibility to load only what’s necessary. Applying them strategically improves both the initial load time and the user experience throughout the app, especially as users navigate or interact more deeply with various parts of the application.