You've probably been in a situation where you visited a website and that site took a bit of time to load, you most likely had to sit down waiting as the loading animation repeated itself. Slow loading speeds are often due to the large amount of code or data the site is loading at once. Having to wait for the site to load could be extremely annoying, not to mention a waste of time.
Trying to navigate through the website as the website continued to load was probably not the best experience, and you probably had to wait until the browser had loaded all the code necessary for the site to function.
If you're a React developer, you can massively speed up site load time by indicating an order of render for components using lazy()
and <Suspense>
.
React lazy()
lazy()
is a function that relies on dynamic import and reduces your website or web app load time by code splitting.
Lazy-loading a component tells the browser that the component isn't a priority, the browser will load all other components and load a lazy-loaded component only when a user navigates to that component or route.
Lazy loading offers several advantages, some of which are:
- Cut down resources needed to load a site
- Reduce the time it takes a user to interact with a site
- Improve a website's user experience
Is Lazy loading necessary?
We'll answer this using an example. The code snippet below is from a React app with a Navbar
component and two other components named PageOne
and PageTwo
import PageOne from "./components/PageOne";
import PageTwo from "./components/PageTwo";
import { Routes, Route } from "react-router-dom";
import Navbar from "./components/Navbar";
function App() {
return (
<>
<Navbar />
<Routes>
<Route path="/" element={<PageOne />} />
<Route
path="pagetwo" element={<PageTwo />}
/>
</Routes>
</>
);
}
export default App;
The PageOne
and PageTwo
components contain two images each.
On initial load, we land on our index or home page shown in the image below
I have not navigated to the Second Page/PageTwo component.
However, the browser has already loaded all the components in the background. To confirm this, I'll navigate to the Sources tab in the Dev tools of chrome browser(If you use Firefox this tab is called 'debugging')
There's an arrow pointing to the components folder in the file tree of the React App on the left side of the image. You can see the browser has already loaded the PageTwo component even though I haven't navigated there yet. This could be an unnecessary drain of user resources, you might wonder, how?
Let's assume you're a content creator that has certain content available for paid users only, loading that content in the background for free users even though they can't access it would not make much sense.
Lazy-loading that premium content will fix this and prevent the browser from loading that content unless a user can access it.
Getting Started with lazy()
Lazy-loading a component takes a different approach from the traditional import() method. I've modified the App component to demonstrate lazy loading
import { lazy, Suspense} from "react";
import { Routes, Route } from "react-router-dom";
import Navbar from "./components/Navbar";
import PageOne from "./components/PageOne";
const PageTwo = lazy(() => {import("./components/PageTwo")});
function App() {
return (
<>
<Navbar />
<Routes>
<Route path="/" element={<PageOne />} />
<Route
path="pagetwo"
element={
<Suspense fallback="Loading...">
<PageTwo />
</Suspense>
}
/>
</Routes>
</>
);
}
export default App;
Let's go over the modifications made to the App component
To lazy load a component, the first thing you need to do is import lazy and Suspense from React and you can see this below.
import { lazy, Suspense } from 'react'
The next step to take is to import your component in the lazy function block and assign it to a variable. You can see this in the example below. The PageTwo component is now being imported in the lazy function block and declared as a variable.
const PageTwo = lazy(() => {import("./components/PageTwo")});
Note - Lazy imports should be done outside a function component and not inside. This means you have to declare them at the top with any other exports
When you're done importing your lazy component, you need to wrap it in <Suspense>
. A lazy-loaded component or route must be wrapped in <Suspense>
<Suspense>
displays a fallback whenever a user navigates to a component and the component is being loaded. You'll learn more about <Suspense>
and fallback
as you read on
The fallback
in <Suspense>
is used to display any React component or text you want to render while your component loads. A fallback
is important as it gives visual feedback that the component is loading.
<Route
path="pagetwo"
element={
<Suspense fallback="Loading...">
<PageTwo />
</Suspense>
}
/>
If you're wrapping a route component in Suspense
, it has to be within the curly braces of the Route element
.
The route has now been lazy-loaded and after reloading the React App the change is evident in the Dev tools
Check it out, the PageTwo
component was not loaded by the browser on initial render. The component will only be loaded when I navigate there. I've included an animated image below to show this in effect.
The browser only loaded PageTwo
when I navigated there and not on the initial render as it did before. This is a huge performance boost as the code and files loaded on the initial render are now smaller than before. Now that you've seen how good lazy-loading is, it's time to see what <Suspense>
does.
React Suspense
The initial explanation of <Suspense>
was brief and in this section, you'll learn more about <Suspense>
. You'll learn:
How to use one
<Suspense>
for multiple components.How to use multiple
<Suspense>
for different componentsHow to add an
ErrorBoundary
to<Suspense>
The PageTwo
component used in the section discussing lazy loading contains a <Suspense>
with a fallback
with the text "Loading..." The image below shows what the suspense displayed while the component was loaded in the background
A <Suspense>
fallback
would normally be a loader component with a spinner or a skeleton loader, but we'll keep things simple in this project.
Something worth mentioning is that you can use <Suspense>
on components that aren't lazy loaded.
Wrap multiple elements in one suspense
You can wrap several components in a single <Suspense>
. Multiple components wrapped in a <Suspense>
will be loaded at the same time with a single fallback
.
The code below has three components wrapped in a single <Suspense>
.
import LightComponent from "./components/LightComponent";
import { lazy, Suspense } from "react";
const MediumComponent = lazy(() =>(import("./components/MediumComponent")
));
const HeavyComponent = lazy(() => (import("./components/HeavyComponent")
));
function App() {
return (
<section>
<Suspense fallback="Loading">
<LightComponent />
<MediumComponent />
<HeavyComponent />
</Suspense>
</section>
);
}
export default App;
There are three components of varying render times in the code snippet above, they are:
LightComponent
- Contains a simple text.MediumComponent
- Contains an image.HeavyComponent
- Contains an image.
All the components are in the same <Suspense>
with a fallback
. MediumComponent
and HeavyComponent
are lazy-loaded, You may have noticed that LightComponent isn't lazy-loaded but still works with <Suspense>
. As I mentioned, you don't need to lazy load a component for it to work with <Suspense>
Placing all three components in the same <Suspense>
means all three components will be completely loaded before rendering. The result is the image below
One or all of the three components is not ready to render to the screen, this causes the fallback
to be displayed until all components are ready. After fully loading all the components, it renders them all at once
You can see from the above example that wrapping all your elements in one <Suspense>
will load all your components at once, even if some of the components are already loaded and ready to render. The page will only display the components when everything is loaded and ready to render.
Nesting suspense
You've seen that wrapping several elements in the same <Suspense>
might not always be the best option. You can wrap your components in different <Suspense>
if you have components with varying sizes.
Check the code snippet below.
import LightComponent from "./components/LightComponent";
import { lazy, Suspense } from "react";
const MediumComponent = lazy(() =>(import("./components/MediumComponent")
));
const HeavyComponent = lazy(() => (import("./components/HeavyComponent")
));
function App() {
return (
<section>
<Suspense>
<Suspense fallback="Loading Light Component">
<LightComponent />
</Suspense>
<Suspense fallback="Loading Medium Component">
<MediumComponent />
</Suspense>
<Suspense fallback="Loading Heavy Component">
<HeavyComponent />
</Suspense>
</Suspense>
</section>
);
}
export default App;
Notice how each component has its own <Suspense>
. Wrapping each component with its <Suspense>
means it will now be rendered in order of readiness. The order of render for our App would now be:
The entire
App Component
would start loading with a fallback ofLoading...
LightComponent
will get rendered to the screen immediately after it finishes loading.
The fallbacks for
MediumComponent
andHeavyComponent
will be displayed as those components continue fetching data.MediumComponent
will be rendered once fully loaded while the fallback forHeavyComponent
remains asHeavyComponent
keeps fetching data.
-
HeavyComponent
gets rendered immediately after it fully loads.
You've seen how <Suspense>
can greatly improve the quality of your websites or web apps. However, just because you can wrap multiple components in <Suspense>
does not mean you should. You should wrap only heavy or lazy-loaded components in <Suspense>
, Wrapping components with only texts or light images in suspense would not make much sense.
Some things you can wrap in <Suspense>
are:
A component that makes a call to an API
A component with a lot of images
Routes
“Simplicity boils down to two steps: Identify the essential. Eliminate the rest.”
― Leo Babauta
Improve Suspense with an Error Boundary
You can level up your code even further by adding an error boundary to <Suspense>
. An error boundary catches errors in your code that could otherwise break your app.
If a component wrapped in <Suspense>
without an error boundary throws an error, your app will only display a blank page which wouldn't be very helpful. <Suspense>
with an <ErrorBoundary>
, however, will display a fallback instead of a blank page
Add an error boundary
To add <ErrorBoundary>
to <Suspense>
, you need to install react-error-boundary
as a dependency using either npm
or yarn
install.
Follow these steps to add <ErrorBoundary>
to <Suspense>
.
Navigate to your terminal and type
npm install react-error-boundary
if you use the node package manager oryarn add react-error-boundary
if you use yarn.-
Import
<ErrorBoundary>
into your app at the top
import { ErrorBoundary } from "react-error-boundary"
-
Wrap your suspense in
<ErrorBoundary>
and provide a fallback for<ErrorBoundary>
. Using a code snippet from one of our earlier examples to demonstrate this, we have
<ErrorBoundary fallback={<p>Something went wrong</p>}> <Suspense fallback="Loading Heavy Component"> <HeavyComponent /> </Suspense> </ErrorBoundary>
The fallback makes sure you or your users can see an error message instead of a blank page, doing this greatly improves the user experience of your website.
You can go one step further to improve ErrorBoundary
by adding an ErrorFallback
component instead of a simple text, with an ErrorFallback
component you can display a proper error message, together with a button to refresh the page or reset your app state. You can create an ErrorFallback
by following these steps.
-
Create a new React component called
ErrorFallback
and in that component add the following code
const ErrorFallback = ({ error, resetErrorBoundary }) => { return ( <div> <p>Something went wrong</p> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}>Try again</button> </div> ) } export default ErrorFallback
Error Fallback takes in two props:
error
is the error thrown by the component wrapped in the error boundary.resetErrorBoundary
is a function that resets your state or App to its original state before an error was thrown -
Import the
ErrorFallback
component in your App component or wherever you used the error boundary
import ErrorFallback from "./components/ErrorFallback";
-
Replace the
fallback
prop in the error boundary with a new prop calledFallbackComponent
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => /*add your code to reset state here*/}> <Suspense fallback="Loading Heavy Component"> <HeavyComponent /> </Suspense> </ErrorBoundary>
Add the
ErrorFallback
component to theFallbackComponent
Add a function called
onReset
toErrorBoundary
theonReset
function takes in a code used to reset the state of the parent component to its original state before an error was thrown. It's up to you to provide the code
Conclusion
In this article you have seen how to:
Lazy-load components.
Add
<Suspense>
to or Suspend your components.Add
fallback
to<Suspense>
.Add
ErrorBoundary
to<Suspense>
to catch any errors.
You've also seen that you need to add <Suspense>
to lazy components, but you don't need to lazy load a component before using <Suspense>
Add lazy()
and <Suspense>
to appropriate sections of your code and you'll improve your website's user experience a lot. If you're interested in learning further, you can check the official react docs.
If you have any comments or questions please feel free to ask them or reach out to me on Twitter where I'm most active
Top comments (0)