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.
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.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 loadingHeavyDashboard component will only load when it is renderedIn 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>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}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.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}Lazy loading is especially useful in the following cases:
Large applications with many components, where the user typically only uses a small part of the functionality during one session
Rarely used features - for example, an admin panel that is used only by a few users
Heavy dependencies - components that use large libraries (e.g., text editors, maps, charts)
Alternative user paths - when different users use different parts of the application depending on their role
Suspense Higher in the Component TreeInstead 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}Split code along natural application boundaries:
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.
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.Lazy loading may work great on fast developer connections, but always test how the user experience looks on slower connections.
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;Lazy loading is a powerful React application optimization technique that allows:
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.