Suspense and lazy loading

In brief

  • Technique used to optimize initial loading time of a web page.
  • Dynamic import() statement provided by JavaScript engines makes it possible.
    • Using it you can delay loading the module until later.
    • It allows splitting the application into a bunch of small chunks and loading them dynamically only when needed.

Example with routing

One common use case is to split into chunks different application pages based on the routes. It’s known as navigation-based lazy loading as it organically works with routing. When user changes the page only then it’s getting loaded and cached for future re-uses.

import { Routes, Route } from "react-router-dom";
import React, { lazy } from "react";

const Dashboard = lazy(() => import("./screens/Dashboard"));
const UserProfile = lazy(() => import("./screens/UserProfile"));

function App() {
  return (
    <Routes>
      <Route path="/dashboard" element={<Dashboard />} />
      <Route path="/users/:id" element={<UserProfile />} />
    </Routes>
  );
}

export default App;
Route-based lazy loading

As you’ve may noticed we’re utilizing lazy construct provided by React. It lets you defer loading component’s code until it is rendered for the first time.

Another example when the technique shines is interaction-based lazy loading. Think of the scenario when user expands the section and it dynamically loads the content. Or when user opens a complicated modal and we load the script at the same time. When you have a dynamic section of such sort it might be a good idea applying lazy loading.

Benefits

  • Smaller bundle size
  • Faster initial loading time, as there is less script to load
  • A greater number of smaller requests loaded in parallel
  • Code that isn’t changed regularly can be cached long-term

<Suspense />

You can think of <Suspense /> as a solution for showing loading indicator while the component is lazy loading.

Usage is simple and straightforward. All you need to do is wrap lazy components into <Suspense /> and specify loading UI in fallback property.

import { Routes, Route } from "react-router-dom";
import React, { lazy, Suspense } from "react";

const Dashboard = lazy(() => import("./screens/Dashboard"));
const UserProfile = lazy(() => import("./screens/UserProfile"));

import LoaderIndicator from "./LoaderIndicator";

export default function App() {
  return (
    <Suspense fallback={<LoaderIndicator />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/users/:id" element={<UserProfile />} />
      </Routes>
    </Suspense>
  );
}
Suspense as a solution for loading indicators

You can have multiple Suspense components with different loading components. When needed, the closest one from JSX tree will be used.

References

  1. Small Bundles, Fast Pages: What To Do With Too Much JavaScript - Calibre
  2. Improving JavaScript Bundle Performance With Code-Splitting
  3. A Complete Guide to React Router: Everything You Need to Know
  4. Understanding React dynamic imports for faster websites