Lazy loading (and preloading) components in React 16.6

Rodrigo Pombo
Nov 26, 2018 · 6 min read

React 16.6 adds a new feature that makes code splitting easier: React.lazy().

Let’s see how and why to use this feature with a small demo.

We have an app that shows a list of stocks. When you click on one stock it shows you a chart:

Image for post
Image for post
Try it

That’s all it does. You can read the full code in the github repo (also check the pull requests to see the diffs and a running version of the app for each change we’ll do).

For this post, we only care about what’s in the App.js file:

We have an App component that receives a list of stocks and shows a <StockTable/>. When a stock is selected from the table, the App component shows a <StockChart/> for that stock.

What’s the problem? Well, we want our app to be blazing fast and show the <StockTable/> as fast as possible, but we are making it wait until the browser downloads (and uncompresses and parses and compiles and runs) the code for <StockChart/>.

Let’s see a trace of how long it takes to display the <StockTable/>:

Image for post
Image for post
Trace without lazy loading

It takes 2470 ms to display the StockTable (with a simulated Fast 3G network and a 4x slowdown CPU).

What’s in those (compressed) 125KB we are shipping to the browser?

Image for post
Image for post
Webpack Budle Analyzer Report

As expected, we have react, react-dom, and some react dependencies. But we also have moment, lodash and victory, which we only need for <StockChart/>, not for <StockTable/>.

What could we do to avoid <StockChart/> dependencies to slow down the loading of <StockTable/>? We lazy-load the component.

Using a dynamic import we can split our bundled javascript in two, a main file with just the code we need for displaying <StockTable/> and another file with the code and the dependencies that <StockChart/> needs.

This technique is so useful that React 16.6 added an API for making it easier to use with React components: React.lazy().

In order to use React.lazy() in our App.js we make two changes:

First we replace the static import with a call to React.lazy() passing it a function that returns the dynamic import. Now the browser won’t download ./StockChart.js (and its dependencies) until we render it for the first time.

But what happens when React wants to render <StockChart/> and it doesn’t have the code yet? That’s why we added <React.Suspense/>. It will render the fallback prop instead of its children until all the code of all its children is loaded.

Now our app will be bundled in two files:

Image for post
Image for post
Webpack Budle Analyzer Report

The main js file is 36KB. The other file is 89KB and has the code from ./StockChart and all its dependencies.

Let’s see again how much it takes the browser to show the <StockTable/> with these changes:

Image for post
Image for post
Trace with lazy loading

The browser takes 760 ms to download the main js file (instead of 1250 ms) and 61 ms to evaluate the script (instead of 487 ms). <StockTable/> is displayed in 1546 ms (instead of 2470 ms).

We made our app load faster. But now we have another problem:

Image for post
Image for post
Notice the “Loading…” before showing the chart (try it)

The first time the user clicks on an item the “Loading…” fallback is shown. That’s because we need to wait until the browser loads the code for <StockChart/>.

If we want to get rid of the “Loading…” fallback, we will have to load the code before the user clicks the stock.

One simple way of preloading the code is to start the dynamic import before calling React.lazy():


The component will start loading when we call the dynamic import, without blocking the rendering of <StockTable/>.

Take a look at how the trace changed from the original eager-loading app:

Image for post
Image for post
Eager Loading vs Lazy Loading

Now, the user will only see the “Loading…” fallback if they click a stock in less than 1 second after the table is displayed. Try it.

You could also enhance the lazy function to make it easier to preload components whenever you need:

For our small demo app that’s all we need. For bigger apps the lazy component may have other lazy code or data to load before it can be rendered. So the user would still have to wait for those.

Another approach for preloading the component is to actually render it before we need it. We want to render it but we don’t want to show it, so we render it hidden:


React will start loading <StockChart/> the first time the app is rendered, but this time it will actually try to render <StockChart/> so if any other dependency (code or data) needs to be loaded it will be loaded.

We wrapped the lazy component inside a hidden div so it doesn’t show anything after it is loaded. And we wrapped that div inside another <React.Suspense/> with a null fallback so it doesn’t show anything while it’s being loaded.

Note: hidden is the HTML attribute for indicating that the element is not yet relevant. The browser won’t render elements with this attribute. React doesn’t do anything special with that attribute(but it may start giving hidden elements a lower priority in future releases).

This last approach is useful in many cases but it has some problems.

First, the hidden attribute for hiding the rendered lazy component isn’t bulletproof. For example, the lazy component could use a portal which won’t be hidden (there is a hack that doesn’t require an extra div and also work with portals, but it’s a hack, it will break).

Second, even if the component is hidden we are still adding unused nodes to the DOM, and that could become a performance problem.

A better aproach would be to tell react to render the lazy component but without comitting it to the DOM after it’s loaded. But, as far as I know, it isn’t possible with the current version of React.

Another improvement we could do is to reuse the elements we are rendering when preloading the chart component, so when we want to actually display the chart React doesn’t need to create them again. If we know what stock the user will click we could even render it with the correct data before the user clicks it (like this).

That’s all. Thanks for reading.

For more stuff like this follow @pomber on twitter.


Sign up for Get Better Tech Emails via


how hackers start their afternoons. the real shit is on Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Rodrigo Pombo

Written by • @pomber

Elijah McClain, George Floyd, Eric Garner, Breonna Taylor, Ahmaud Arbery, Michael Brown, Oscar Grant, Atatiana Jefferson, Tamir Rice, Bettie Jones, Botham Jean

Rodrigo Pombo

Written by • @pomber

Elijah McClain, George Floyd, Eric Garner, Breonna Taylor, Ahmaud Arbery, Michael Brown, Oscar Grant, Atatiana Jefferson, Tamir Rice, Bettie Jones, Botham Jean

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store