Suspense with Lazy-Loaded Routes

Loading

Suspense with Lazy-Loaded Routes in React

In React, Suspense and React.lazy are powerful tools that enable code splitting and lazy loading for components, including routes. By using Suspense with lazy-loaded routes, you can improve your application’s performance by loading routes and their components only when needed, instead of loading everything upfront. This is particularly useful for large applications with many routes or heavy components.

What is React.lazy?

React.lazy is a function that enables dynamic import of a component, meaning the component is only loaded when it’s actually needed. It’s a way to split your code and load it in chunks, which can lead to faster initial loading times and more efficient resource use.

What is Suspense?

Suspense is a component that you can wrap around code that is being lazy-loaded. It allows you to specify a fallback UI (like a loading spinner or message) while the component is being loaded.

Using Suspense with Lazy-Loaded Routes

In React Router, you can combine Suspense and React.lazy to lazy-load routes, which means the components associated with those routes will only be loaded when the route is visited, not before.

Here’s how to set up Suspense with lazy-loaded routes:

1. Basic Setup of React.lazy with Suspense

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

// Lazy load components
const HomePage = lazy(() => import('./HomePage'));
const AboutPage = lazy(() => import('./AboutPage'));
const ContactPage = lazy(() => import('./ContactPage'));

const App = () => {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/contact">Contact</Link>
      </nav>

      {/* Suspense wrapper for lazy-loaded routes */}
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/about" element={<AboutPage />} />
          <Route path="/contact" element={<ContactPage />} />
        </Routes>
      </Suspense>
    </Router>
  );
};

export default App;

Explanation:

  • React.lazy: We use React.lazy to import the HomePage, AboutPage, and ContactPage components. These components will be loaded only when the user navigates to their respective routes.
  • Suspense: We wrap the Routes in a Suspense component to handle the fallback UI while the component is being loaded.
    • The fallback prop is a placeholder UI (e.g., a loading spinner or message) that will be displayed until the lazy-loaded component is ready.

2. Lazy-Loading with Nested Routes and Suspense

If you have a more complex app with nested routes, you can still use Suspense with lazy-loaded components. This is particularly useful when you want to lazy-load large sections or modules of your application.

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

// Lazy load components
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
const Settings = lazy(() => import('./Settings'));

const App = () => {
  return (
    <Router>
      <nav>
        <Link to="/dashboard">Dashboard</Link>
      </nav>

      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/dashboard" element={<Dashboard />}>
            <Route path="profile" element={<Profile />} />
            <Route path="settings" element={<Settings />} />
          </Route>
        </Routes>
      </Suspense>
    </Router>
  );
};

export default App;

Explanation:

  • Nested Routes: The /dashboard route has nested routes (/dashboard/profile and /dashboard/settings) that are lazy-loaded as well.
  • Suspense: The entire route configuration is wrapped in a Suspense component, ensuring that all lazy-loaded components, including nested ones, have a fallback UI while loading.

3. Lazy-Loading and Error Boundaries

In real-world applications, lazy-loaded components might fail to load due to network issues or other reasons. You can handle such errors with Error Boundaries along with Suspense.

Adding Error Boundaries:

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

// Lazy load components
const HomePage = lazy(() => import('./HomePage'));
const AboutPage = lazy(() => import('./AboutPage'));

const ErrorBoundary = ({ children }) => {
  return (
    <React.ErrorBoundary fallback={<div>Something went wrong!</div>}>
      {children}
    </React.ErrorBoundary>
  );
};

const App = () => {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>

      {/* Error Boundary and Suspense */}
      <ErrorBoundary>
        <Suspense fallback={<div>Loading...</div>}>
          <Routes>
            <Route path="/" element={<HomePage />} />
            <Route path="/about" element={<AboutPage />} />
          </Routes>
        </Suspense>
      </ErrorBoundary>
    </Router>
  );
};

export default App;

Explanation:

  • Error Boundaries: The ErrorBoundary component is used to catch any errors that occur while loading the lazy-loaded components and display a fallback UI.
  • Suspense: Suspense still handles the loading state, while ErrorBoundary ensures that any errors are caught and handled gracefully.

4. Using Suspense for Route-based Data Fetching

Another common pattern is to use Suspense for data fetching when routes are loaded. React’s Suspense can be used alongside a data-fetching library (such as React Query, Apollo Client, or custom fetchers) to manage loading states.

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

// Lazy load components
const PostList = lazy(() => import('./PostList'));

const App = () => {
  return (
    <Router>
      <nav>
        <Link to="/posts">Posts</Link>
      </nav>

      <Suspense fallback={<div>Loading posts...</div>}>
        <Routes>
          <Route path="/posts" element={<PostList />} />
        </Routes>
      </Suspense>
    </Router>
  );
};

export default App;

In this example, PostList is a component that fetches data for posts. The Suspense component will show a loading state while the component fetches the data, and then display the data once it’s available.


Performance Benefits

  • Faster Initial Load: By lazily loading routes and components, the browser doesn’t have to load everything upfront, leading to faster initial render times.
  • Reduced Bundle Size: Lazy loading breaks the JavaScript bundle into smaller pieces, reducing the amount of JavaScript that needs to be downloaded initially.
  • On-demand Loading: Code for routes and components is only fetched when needed, optimizing performance, especially for large applications.

Leave a Reply

Your email address will not be published. Required fields are marked *