Lazy Load - The Better Way

Lazy Load - The Better Way

Say no more to the 'Glimmer' / 'Flash' Issue

What is lazy Load ?
In the realm of web applications, "lazy loading" reigns supreme as a go-to method for ensuring rapid content display to users as soon as they navigate to the site. In the react world, react provides a nifty method lazy for doing this, This method 'lazy' lets us defer the loading of the component until its rendered for the first time.

Lets assume you are building an application which has multiple tabs / page layout and you are code splitting and you are lazy loading all the pages / tabs / component because your user could land on any of the routes which would render respective component.

And after you do this - you ship your application to prod and you open up your chrome lighthouse and hit analyse page load and Voila ! You see drastic improvements in vitals and you are happy but there’s a small catch to this, you start noticing that whenever your application renders the lazy load component for the first time it shows the fall back component of react suspense and since the chunk is considerably small it sort of glimmers / flashes the fallback component and it’s really a bad UX.

Humm, what’s the solution, what can we do about it ?

well, if we think about it - once the first component is rendered , time won’t remain much crucial and we have bandwidth of time in which we can preload rest of the lazy loading components so that when the application has to render other lazy load components it would already have that component preloaded and there wouldn’t be need of showing the fall back component of react suspense and this results in eradication of glimmer / flash issue.

now we have broken the issue down into three smaller issues which we have to tackle

  1. Preloading - Loading of component at our wish.

  2. HOC to makes things easy - Constructing a mechanism to recognise when the first component gets rendered and preload the rest of lazy load components.

  3. Dealing with Routes - Combining the above two with the routing mechanism to achieve the main goal of eradication of glimmer in router base / tab based application.

1. Preloading

In order to preload a lazy load component I found out a library called react-lazy-with-preload which provides a utility function to preload the lazy component.

how to preload

  1. Install the package
    npm install react-lazy-with-preload

  2. import the component you want to lazy load like shown below

     import { Suspense } from "react";
     import { lazyWithPreload } from "react-lazy-with-preload";
     const OtherComponent = lazyWithPreload(() => import("./OtherComponent"));
    
  3. Trigger loading of your lazy component at your desired moment like below

     OtherComponent.preload();
    

2. HOC to makes things easy

Lets create a HOC which would receive three props - Children, A callback function which would pre load all the remaining lazy loading component, A boolean 'isPreloaded' to know if the components are already preloaded so that we don't call the function again.

import React, { useEffect } from 'react';
// PreLoadHOC.js
const PreLoadHOC = (props) => {
  useEffect(() => {
    if (!props.isPreloaded) {
      setTimeout(() => {
        props.startPreload();
      }, 500);
    }
  }, []);

  return props.children;
};

export default PreLoadHOC;

3. Dealing with Routes

Let's consider the scenario where we are importing multiple lazy load components and we are rendering them based on the route we are in, for this lets use the HOC component we created above and pass in a callback function which would preLoad all the lazy load components on the render of first lazy load component.

// App.js
import React, { useEffect, useState, Suspense } from 'react';
import { lazyWithPreload } from 'react-lazy-with-preload';
import { BrowserRouter, Route } from 'react-router-dom';
import PreLoadHOC from './PreLoadHOC.js';

const ComponentOne = lazyWithPreload(() => import('./ComponentOne'));
const ComponentTwo = lazyWithPreload(() => import('./ComponentOne'));
const ComponentThree = lazyWithPreload(() => import('./ComponentOne'));

const App = () => {
  const [isPreloaded, setIsPreloaded] = useState(false);

  // Preload the components
  const startPreload = () => {
    ComponentOne.preload();
    ComponentTwo.preload();
    ComponentThree.preload();
    setIsPreloaded(true);
  };

  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Route
          path="/One"
          Component={(
            <PreLoadHOC
              startPreload={startPreload}
              isPreloaded={isPreloaded}
            >
              <ComponentOne />
            </PreLoadHOC>
        )}
        />
        <Route
          path="/Two"
          Component={(
            <PreLoadHOC
              startPreload={startPreload}
              isPreloaded={isPreloaded}
            >
              <ComponentTwo />
            </PreLoadHOC>
        )}
        />
        <Route
          path="/Three"
          Component={(
            <PreLoadHOC
              startPreload={startPreload}
              isPreloaded={isPreloaded}
            >
              <ComponentThree />
            </PreLoadHOC>
        )}
        />
      </Suspense>
    </BrowserRouter>
  );
};

export default App;

With the above setup, when the user lands on any of the route ( /one , /two or /three ) , that component gets lazy loaded and mounts and on mounting of that component it triggers the useEffect of the <PreLoadHOC /> in which we are calling the function to preload all the lazy load components. This results as when the user moves to other routes/tab it would have the lazy load component preloaded and would not have to show the fallback component of <Suspense /> and

We will not be having glimmer effect from now on !

Fin.

Please do subscribe to my newsletter for more such articles !