We use cookies to enhance your experience on the site
CodeWorlds

Lazy Loading Components on a Cosmic Journey

During a long intergalactic journey, we don't need to load all the equipment onto the spaceship right away. Similarly in React applications, especially larger ones, we don't need to load all components right away during application initialization. This is where lazy loading comes to our aid.

What is Lazy Loading?

Lazy loading is a performance optimization technique that involves loading components, modules, or resources only when they are actually needed, instead of loading them upfront during application initialization. In React, we accomplish this using the

React.lazy()
function combined with
Suspense
.

Basics of Lazy Loading in React

To implement lazy loading of a component, instead of the standard component import:

1import HeavyDashboard from './HeavyDashboard';

We use the

React.lazy()
function with a dynamic import:

1import React, { lazy, Suspense } from 'react';
2
3// Lazy loading of a component
4const HeavyDashboard = lazy(() => import('./HeavyDashboard'));
5
6function App() {
7  return (
8    <div>
9      <h1>Flight Control Center</h1>
10
11      {/* Suspense provides a fallback component during loading */}
12      <Suspense fallback={<div>Loading control panel...</div>}>
13        <HeavyDashboard />
14      </Suspense>
15    </div>
16  );
17}

In this example:

  • lazy()
    accepts a function that must call a dynamic
    import()
  • Suspense
    displays a fallback (replacement component) during lazy component loading
  • The actual
    HeavyDashboard
    component will only load when it is rendered

Lazy Loading in React Router

In applications using React Router, we can lazily load components for individual routes. This way the initial application bundle will be smaller, and the user will only download code for the parts of the application they visit:

1import React, { lazy, Suspense } from 'react';
2import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
3
4// Lazily loaded components
5const MissionControl = lazy(() => import('./MissionControl'));
6const SpaceshipSettings = lazy(() => import('./SpaceshipSettings'));
7const GalacticMap = lazy(() => import('./GalacticMap'));
8const CrewQuarters = lazy(() => import('./CrewQuarters'));
9
10// Always loaded component (small and frequently used)
11import NavigationBar from './NavigationBar';
12
13function App() {
14  return (
15    <Router>
16      <NavigationBar />
17
18      <Suspense fallback={<div className="loading-screen">Preparing module...</div>}>
19        <Routes>
20          <Route path="/" element={<HomePage />} />
21          <Route path="/mission-control" element={<MissionControl />} />
22          <Route path="/spaceship-settings" element={<SpaceshipSettings />} />
23          <Route path="/galactic-map" element={<GalacticMap />} />
24          <Route path="/crew-quarters" element={<CrewQuarters />} />
25        </Routes>
26      </Suspense>
27    </Router>
28  );
29}

In this example, only components for the currently visited route will be loaded. We can also add individual

Suspense
components for each route if we want different loading indicators:

1<Routes>
2  <Route path="/" element={<HomePage />} />
3  <Route
4    path="/mission-control"
5    element={
6      <Suspense fallback={<div>Initializing mission control panel...</div>}>
7        <MissionControl />
8      </Suspense>
9    }
10  />
11  <Route
12    path="/galactic-map"
13    element={
14      <Suspense fallback={<div>Loading galactic map...</div>}>
15        <GalacticMap />
16      </Suspense>
17    }
18  />
19</Routes>

Advanced Lazy Loading Techniques

Prefetching (Pre-loading)

Sometimes we know with high probability that the user will visit a specific route. We can then pre-load the component before the user actually clicks the link:

1import { lazy } from 'react';
2
3// Lazy component declaration
4const GalacticMap = lazy(() => import('./GalacticMap'));
5
6// Navigation component with prefetching
7function NavigationLink({ to, children }) {
8  // Function for pre-loading
9  const prefetchComponent = () => {
10    // Dynamic import as Promise - starts loading
11    import('./GalacticMap');
12  };
13
14  return (
15    <Link
16      to={to}
17      onMouseOver={prefetchComponent} // Start loading on mouse hover
18      onFocus={prefetchComponent}     // For accessibility, loading on focus
19    >
20      {children}
21    </Link>
22  );
23}
24
25// Usage in a component
26function Navigation() {
27  return (
28    <nav>
29      <NavigationLink to="/galactic-map">Galactic Map</NavigationLink>
30    </nav>
31  );
32}

Route Grouping (route-based code splitting)

We can group related lazy-loaded components to reduce the number of separate network requests:

1// Grouping cockpit-related components
2const CockpitModule = lazy(() => import('./modules/CockpitModule'));
3
4// Using the grouped module in routing
5<Route path="/cockpit/*" element={<CockpitModule />} />
6
7// In CockpitModule.js we have internal routes
8function CockpitModule() {
9  return (
10    <Routes>
11      <Route path="/" element={<CockpitOverview />} />
12      <Route path="/navigation" element={<NavigationControls />} />
13      <Route path="/engines" element={<EngineControls />} />
14      <Route path="/life-support" element={<LifeSupportSystems />} />
15    </Routes>
16  );
17}

In this approach, all cockpit components will be fetched as one chunk of code when the user enters any

/cockpit/*
route.

Error Handling During Loading

In space, we must always be prepared for unforeseen situations. In the case of lazy loading, it may happen that the component loading attempt fails (e.g., due to network problems):

1import React, { lazy, Suspense } from 'react';
2import { ErrorBoundary } from 'react-error-boundary';
3
4const GalacticMap = lazy(() => import('./GalacticMap'));
5
6function ErrorFallback({ error, resetErrorBoundary }) {
7  return (
8    <div className="error-container">
9      <h2>Problem encountered while loading the module</h2>
10      <p>Error: {error.message}</p>
11      <button onClick={resetErrorBoundary}>
12        Try again
13      </button>
14    </div>
15  );
16}
17
18function MapSection() {
19  return (
20    <ErrorBoundary
21      FallbackComponent={ErrorFallback}
22      onReset={() => {
23        // Optional state reset logic
24      }}
25    >
26      <Suspense fallback={<div>Loading galactic map...</div>}>
27        <GalacticMap />
28      </Suspense>
29    </ErrorBoundary>
30  );
31}

When is Lazy Loading Worth Using?

Lazy loading is especially useful in the following cases:

  1. Large applications with many components, where the user typically only uses a small part of the functionality during one session

  2. Rarely used features - for example, an admin panel that is used only by a few users

  3. Heavy dependencies - components that use large libraries (e.g., text editors, maps, charts)

  4. Alternative user paths - when different users use different parts of the application depending on their role

Best Practices

1. Place
Suspense
Higher in the Component Tree

Instead of wrapping each lazy-loaded component in a separate

Suspense
, it's often better to place one
Suspense
higher in the tree:

1function App() {
2  return (
3    <Suspense fallback={<GlobalLoadingSpinner />}>
4      <Router>
5        <Routes>
6          {/* Lazy-loaded routes */}
7        </Routes>
8      </Router>
9    </Suspense>
10  );
11}

2. Apply Appropriate Code Splitting Boundaries

Split code along natural application boundaries:

  • Splitting by routes (routing)
  • Splitting by functional modules
  • Splitting between public and private parts of the application

3. Avoid Too-fine Splitting

Too-fine code splitting can lead to many small network requests, which can be less efficient than a few larger ones. Find the golden mean.

4. Always Provide a Good Fallback

Make sure the

fallback
in
Suspense
is visually consistent with the rest of the application and gives the user clear information about the loading state.

5. Test on Real Devices and Slow Connections

Lazy loading may work great on fast developer connections, but always test how the user experience looks on slower connections.

Example Implementation

Here's an example of a more extensive space application with lazy loading:

1import React, { lazy, Suspense } from 'react';
2import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
3import { ErrorBoundary } from 'react-error-boundary';
4
5// Always loaded components
6import MainLayout from './layouts/MainLayout';
7import LoadingScreen from './components/LoadingScreen';
8import ErrorFallback from './components/ErrorFallback';
9
10// Lazy-loaded pages
11const HomePage = lazy(() => import('./pages/HomePage'));
12const Dashboard = lazy(() => import('./pages/Dashboard'));
13const NavigationModule = lazy(() => import('./modules/NavigationModule'));
14const LifeSupportModule = lazy(() => import('./modules/LifeSupportModule'));
15const CommunicationModule = lazy(() => import('./modules/CommunicationModule'));
16const CrewModule = lazy(() => import('./modules/CrewModule'));
17const SettingsPage = lazy(() => import('./pages/SettingsPage'));
18const NotFoundPage = lazy(() => import('./pages/NotFoundPage'));
19
20function App() {
21  return (
22    <Router>
23      <ErrorBoundary FallbackComponent={ErrorFallback}>
24        <MainLayout>
25          <Suspense fallback={<LoadingScreen />}>
26            <Routes>
27              <Route path="/" element={<HomePage />} />
28              <Route path="/dashboard" element={<Dashboard />} />
29
30              {/* Nested modules with their own routes */}
31              <Route path="/navigation/*" element={<NavigationModule />} />
32              <Route path="/life-support/*" element={<LifeSupportModule />} />
33              <Route path="/communication/*" element={<CommunicationModule />} />
34              <Route path="/crew/*" element={<CrewModule />} />
35
36              <Route path="/settings" element={<SettingsPage />} />
37              <Route path="/404" element={<NotFoundPage />} />
38              <Route path="*" element={<Navigate to="/404" replace />} />
39            </Routes>
40          </Suspense>
41        </MainLayout>
42      </ErrorBoundary>
43    </Router>
44  );
45}
46
47export default App;

Summary

Lazy loading is a powerful React application optimization technique that allows:

  1. Reducing the initial JavaScript bundle size
  2. Shortening application loading time
  3. Improving user experience through faster initial rendering
  4. Loading code only when it's actually needed

It's like smart resource management on a spaceship - instead of taking everything at the beginning of the journey, we transport only what's currently needed, saving fuel and speeding up the launch.

Go to CodeWorlds