Lazy loading with React

Photo by Priscilla Du Preez @unsplash

Subscribe to my newsletter and never miss my upcoming articles

Why you need lazy loading

Most of the time, you will have parts of your page that contain code and/or data for a component that won't be visible straight away, unless the user clicks somewhere or scroll the page. Loading all these resources can block the main thread and push how soon users will be able to interact with the page.

This can impact the performance of your website on metrics tools like Time to interactive or GTmetrix.

The faster your users can interact with the page, the better, isn't it?

Fake loading and lazy imports

The Lite Youtube Embed project by Paul Irish is a perfect example of fake loading: it takes a Youtube Video ID and presents only a thumbnail with a play button :

Lite Youtube Embed Demo

When the play button is clicked, this is when the actual Youtube player is being loaded in order to play the video. By doing that, the page load time is drastically decreased.

Here is the resulting page load with Lite Youtube Embed :

Page load without Lite Youtube Embed

And without :

Page load with Lite Youtube Embed

Live demo

Complex dialogs/modals are also a good use case for this. Initially, there is just a button displayed somewhere on the page. When the user clicks on this button, a modal would show up and allow him to perform various operations, quite often including the use of third-party libraries or complex business logic. This is where the import-on-interaction pattern is a really good fit because you won't slow down the page load with code that the user might not even put to use. This is how it is done in Google Docs for the "Share" dialog :

Google Docs Share Dialog

When the button is clicked, the dialog component is loaded, saving 500KB of script for the share feature by deferring its load until user-interaction.

How does this work in React?

It's actually quite surprising how simple it is. When I found out, I immediately wanted to go back on my previous projects and implement it everywhere ๐Ÿ˜…

Here is a really basic example: let's say you would like to use the react-scroll library for a nicely animated "scroll to the top" feature, triggered when a button is clicked. Here is what your component would look like without the import-on-interaction pattern :

import { animateScroll as scroll } from "react-scroll";

const ScrollToTopBtn = () => {
  const handleClick = () => {
    scroll.scrollToTop();
  };

  return (
    <button onClick={handleClick}>
      Scroll to the top !
    </button>
  );
};

And with lazy loading :

const LazyScrollToTopBtn = () => {
  const handleClick = () => {
    import("react-scroll").then(scroll => {
      scroll.animateScroll.scrollToTop();
    });
  };

  return (
    <button onClick={handleClick}>
      Scroll to the top !
    </button>
  );
};

That's it ! Pretty cool, right ? Also, you can use object destructuring to import animateScroll directly :

const handleClick = () => {
  import("react-scroll").then(({animateScroll}) => {
    animateScroll.scrollToTop();
  });
};

React.lazy and Suspense

React comes with a built-in way of "code-splitting" your app, in order to reduce the size of your bundle. But first, what's code-splitting? According to React official documentation :

Code-splitting your app can help you โ€œlazy-loadโ€ just the things that are currently needed by the user, which can dramatically improve the performance of your app. While you havenโ€™t reduced the overall amount of code in your app, youโ€™ve avoided loading code that the user may never need and reduced the amount of code needed during the initial load.

With the React.lazy function and the Suspense Component, you can render a dynamic import as a regular component :

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

Here, OtherComponent will only be loaded when MyComponent is first rendered. Suspense allows you to manage the loading state between the render of MyComponent and the moment OtherComponent will be available. This will result in OtherComponent being in a separate chunk of JavaScript. In a way, it's like showing a component in a loading state, fetch data from an API, and then show the component with the data. Only here it's not data you're "fetching", it's your own components ๐Ÿ˜‡

Note: React.lazy and Suspense are not yet available for server-side rendering. If you want to do code-splitting in a server-rendered app, React team recommends Loadable Components.

No Comments Yet