How to use React Suspense for Code-Splitting?

userMarko Marinović

React 16.6 shipped an interesting feature called Suspense. Suspense allows React to suspend rendering while waiting for something. Loading indicator is displayed during wait time.

Why is this important to us?

We bundle our React apps using tools like webpack and rollup. Our bundle grows as our application grows, especially when we include different third party libraries. Over time, our bundle will be huge and impact loading time of our application. To prevent this from happening, we can start splitting our big bundle into multiple smaller bundles with a process called code-splitting. Code-Splitting is supported by tools such as webpack and rollup. These tools can generate multiple bundles that we can load on demand in runtime. I'll focus on webpack since it's used in this project.

With code-splitting we can give to the user only the code he currently needs, loading additional code as user navigates though the app.

How can Suspense and lazy help us?

Suspense will display the fallback component while bundle is dynamically loaded. It works together with lazy function.

lazy takes a single argument called factory (function returning a promise) and creates a new component of type LazyComponent. LazyComponent will call a factory function on the first render and resulting promise will be used by Suspense to show/hide the fallback component. In the case of code-splitting, we will pass a dynamic import of our component module.

1import { lazy } from 'React';
2
3const Home = lazy(() => import('./pages/Home'));
4const BlogDetails = lazy(() => import('./pages/BlogDetails'));

webpack will see a dynamic import and create a promise that will resolve once the bundle is loaded. You can read more about webpack dynamic imports [here][https://webpack.js.org/guides/code-splitting/#dynamic-imports].

Keep in mind that lazy only supports default exports, so make sure your component module has a default export. If you have a named export, you can re-export it as default to work around this.

Lazy loading pages and blog content

This is the App.ts for this blog project. Each page is dynamically loaded the first time we navigate to it.

1import React, { Suspense, lazy } from 'react';
2import { Router } from '@reach/router';
3import { ErrorBoundary } from 'shared/components/ErrorBoundary';
4import SuspenseFallback from 'shared/components/SuspenseFallback';
5import { posts } from 'data/blog';
6
7const Home = lazy(() => import('./pages/Home'));
8const BlogDetails = lazy(() => import('./pages/BlogDetails'));
9
10const App = () => (
11  <React.StrictMode>
12    <ErrorBoundary>
13      <Suspense fallback={<SuspenseFallback />}>
14        <Router>
15          <Home path="/" />
16          {posts.map((post) => {
17            const { id, slug } = post;
18            return <BlogDetails post={post} key={id} path={`/${slug}`} />;
19          })}
20        </Router>
21      </Suspense>
22    </ErrorBoundary>
23  </React.StrictMode>
24);
25
26export default App;

<SuspenseFallback /> will be displayed while we wait for the bundle to load. You can test this by throttling your internet connection and refreshing the page.

1import React from 'react';
2
3const SuspenseFallback = () => <span>Suspended. Loading data...</span>;
4
5export default SuspenseFallback;

Lazy loading blog post content

The interesting thing is that we don't have to use this concept just for routing. Lazy loading is used in this blog project for content fetching as well. Content for each blog post is in the form of .mdx file.

BlogDetails component will be loaded the first time we click on the blog post. Each blog post has a separate content which will be loaded separately. This allows us to load BlogDetails component once and separately load content depending on the post. Without lazy loading the content, we would have to bundle all .mdx files in the main bundle, drastically increasing our bundle size. mdx files replace database calls in this case.

Blog post data looks like this:

1import { lazy } from 'react';
2
3interface IBlogPost {
4  id: number;
5  title: string;
6  description: string;
7  date: string;
8  slug: string;
9  Content: React.LazyExoticComponent<any>;
10}
11
12const post: IBlogPost = {
13  id: 4,
14  title: 'How to use React Suspense for Code-Splitting?',
15  description:
16    'Suspense allows React to suspend rendering while waiting for something.',
17  date: '10.02.2020 @ 21:30',
18  slug: 'how-to-use-react-suspense-for-code-splitting',
19  Content: lazy(() => import('./content.mdx')),
20};
21
22export default post;

Content is our lazy component which dynamically imports content.mdx file.

BlogDetails component renders lazy Content component, initiating .mdx file load.

1import React from 'react';
2import { RouteComponentProps } from '@reach/router';
3import Layout from 'shared/components/Layout';
4import { StyledHeader } from 'shared/styles/components';
5import { IBlogPost } from 'shared/types/models/blog';
6import MDXWrapper from 'shared/wrappers/MDXProvider';
7import { StyledContent, StyledPublishedOn } from './BlogDetailsStyles';
8
9interface IOwnProps {
10  post: IBlogPost;
11}
12
13type IProps = IOwnProps & RouteComponentProps;
14
15const BlogDetails = (props: IProps) => {
16  const { post } = props;
17  const { title, date, description, slug, Content } = post;
18  const pageMeta = {
19    title,
20    description,
21    slug,
22    date,
23  };
24
25  return (
26    <Layout meta={pageMeta}>
27      <StyledHeader>
28        <h1>{title}</h1>
29        <StyledPublishedOn>Published on {date}</StyledPublishedOn>
30      </StyledHeader>
31      <StyledContent>
32        <MDXWrapper>
33          <Content />
34        </MDXWrapper>
35      </StyledContent>
36    </Layout>
37  );
38};
39
40export default BlogDetails;

If you open the network tab in dev tools you will see that the first time you visit a blog post it loads multiple bundles. Visiting other blog posts will initiate a load of additional content.mdx bundles.

Conclusion

Suspense for code-splitting is a very powerful tool for improving application performance. This is something you can start implementing right now to greately improve performance of your web app. There are more Suspense related things comming in the new concurrent mode for React which is currently in experimental phase.

We use cookies to optimize user experience. By continuing to use our website, you agree to our privacy policy.

Accept