JavaScript and React course Β· Module 6: React Router
Lazy Loading Components on a Cosmic Journey
In this lesson8
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 dynamicimport()Suspensedisplays a fallback (replacement component) during lazy component loading- The actual
HeavyDashboardcomponent 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:
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
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:
- Reducing the initial JavaScript bundle size
- Shortening application loading time
- Improving user experience through faster initial rendering
- 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.
Code for this lesson: App.jsx
1import React from 'react';
2import { BrowserRouter, Routes, Route, Link, useSearchParams } from 'react-router-dom';
3
4// Product list with filtering
5function ProductList() {
6 const [searchParams, setSearchParams] = useSearchParams();
7
8 const category = searchParams.get('category') || 'all';
9 const sortBy = searchParams.get('sort') || 'name';
10 const search = searchParams.get('search') || '';
11
12 const products = [
13 { id: 1, name: 'Space suit', category: 'equipment', price: 5000 },
14 { id: 2, name: 'Astronaut helmet', category: 'equipment', price: 1500 },
15 { id: 3, name: 'Model rocket', category: 'toys', price: 200 },
16 { id: 4, name: 'Teleskop', category: 'tools', price: 3000 },
17 { id: 5, name: 'Galaxy map', category: 'tools', price: 500 },
18 { id: 6, name: 'Astronaut figurine', category: 'toys', price: 50 },
19 ];
20
21 // Filtering
22 let filteredProducts = products;
23 if (category !== 'all') {
24 filteredProducts = filteredProducts.filter(p => p.category === category);
25 }
26 if (search) {
27 filteredProducts = filteredProducts.filter(p =>
28 p.name.toLowerCase().includes(search.toLowerCase())
29 );
30 }
31
32 // Sorting
33 filteredProducts.sort((a, b) => {
34 if (sortBy === 'price') return a.price - b.price;
35 return a.name.localeCompare(b.name);
36 });
37
38 const updateFilter = (key, value) => {
39 const newParams = new URLSearchParams(searchParams);
40 if (value) {
41 newParams.set(key, value);
42 } else {
43 newParams.delete(key);
44 }
45 setSearchParams(newParams);
46 };
47
48 return (
49 <div className="product-list">
50 <h1>Space shop</h1>
51
52 <div className="filters">
53 <div className="filter-group">
54 <label>Search:</label>
55 <input
56 type="text"
57 value={search}
58 onChange={(e) => updateFilter('search', e.target.value)}
59 placeholder="Type a name..."
60 />
61 </div>
62
63 <div className="filter-group">
64 <label>Category:</label>
65 <select
66 value={category}
67 onChange={(e) => updateFilter('category', e.target.value)}
68 >
69 <option value="all">All</option>
70 <option value="equipment">Equipment</option>
71 <option value="tools">Tools</option>
72 <option value="toys">Toys</option>
73 </select>
74 </div>
75
76 <div className="filter-group">
77 <label>Sort:</label>
78 <select
79 value={sortBy}
80 onChange={(e) => updateFilter('sort', e.target.value)}
81 >
82 <option value="name">Name</option>
83 <option value="price">Price</option>
84 </select>
85 </div>
86 </div>
87
88 <div className="current-params">
89 <strong>URL params:</strong> {searchParams.toString() || '(none)'}
90 </div>
91
92 <div className="products">
93 {filteredProducts.map(product => (
94 <div key={product.id} className="product">
95 <h3>{product.name}</h3>
96 <p className="category">{product.category}</p>
97 <p className="price">{product.price} credits</p>
98 </div>
99 ))}
100 {filteredProducts.length === 0 && (
101 <p className="no-results">No products matching criteria</p>
102 )}
103 </div>
104 </div>
105 );
106}
107
108function App() {
109 return (
110 <BrowserRouter>
111 <div className="app">
112 <nav>
113 <Link to="/products">All</Link>
114 <Link to="/products?category=equipment">Equipment</Link>
115 <Link to="/products?category=tools">Tools</Link>
116 <Link to="/products?category=toys&sort=price">Toys (price)</Link>
117 </nav>
118 <Routes>
119 <Route path="/" element={<ProductList />} />
120 <Route path="/products" element={<ProductList />} />
121 </Routes>
122 </div>
123 </BrowserRouter>
124 );
125}
126
127export default App;Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What does the React.lazy() function return in React?
2. What is the role of the Suspense component with lazy loading?
Hands-on tasks in the game
- Code editor
Protected Routes
- Click in order
Arrange the Route syntax with a dynamic parameter:
- Code editor
Lazy loading components
- Vertical ordering
Arrange the steps of lazy loading a component in React: