Lazy Loading
Lazy loading is a way by which we can load content only when they are needed. This is achieved by code-splitting, where we split our app into multiple chunks. The idea here is to serve the user with only the content they can view, and serve the other contents as and when the user visits them.
Route based code-splitting
For example, lets say we have a website where we have /home
, /profile
and /about
routes, and /home
is where the user first lands on. If we can compile the three routes into three bundles, we can serve them as and when the user visits the respective pages. The code for home
route would contain only the code in <Home />
component. And when the user visits /about
, the content for this route will be downloaded and displayed. If we have a fairly large app with lots of routes, this would give us a significant performance gain on initial page load times.
Component based code-splitting
The above example describes what is a route-based code-splitting strategy. We can take this a step further with a component based code-splitting strategy. Lets say we have a heavy form component buried deep in the app which the user would rarely use. It doesn't make sense for us to add it to our main bundle and it's a perfect recipe for lazy-loading.
Dynamic Imports
We can achieve this in React using Dynamic imports. React provides us way by which we can leverage dynamic imports with React.lazy
and Suspense
From React docs.
Lets build our example. We have a dummy form component <HeavyForm />
. It does nothing, but you get the idea.
And, if we want to dynamically import it, we would do something like this
Intersection Observer
If you run the code now, you can see the HeavyForm
is downloaded as a separate js file. This means that HeavyForm
was bundled as a separate chunk and it is not part of our main bundle.
Great! But still, it is downloaded as soon as the page loads. We want this to be downloaded only when it is in the viewport, i.e when the user actually sees it.
This is where Intersection Observer comes in. IntersectionObserver lets us know if the target element is in the viewport or not. We can safely assume that if the IntersectionObserver fires, the target element is in the viewport. We can leverage this and lazily load any component when it is in the viewport.
I'm going to be using react-intersection-observer library, which uses native IntersectionObserver underneath and gives us neat hooks for ease of use.
This is what the complete implementation would look like with IntersectionObserver. react-intersection-observer
gives us useInView
hook, which gives us a ref
and inView
flag. The ref
should be attached to the target element and inView
lets us know if the target element is in the viewport. The threshold
option is a value between 0
and 1
indicating the percentage of element that should be visible before triggering.
Now, <HeavyForm />
would only be downloaded when it is in the viewport.
Conclusion
This technique can be extended to multiple routes and components for easy gains on initial page load times. Remember to strike a balance between the components you lazy-load and components that are added to the main bundle. There is a penalty of network round trip that needs to be made when requesting lazy-loaded content.
Cheers!
You can take a look at the entire source code here
Top comments (6)
I've built a simple wrapper for that sometime ago - you can check it here -> github.com/maciekgrzybek/react-laz-y
There is different way of implementation for the same approach. Your code might look easier but which approach is better?
They are pretty much the same, I just abstracted it out to its own component. Also I'm not saying that one is better than another :)
Great article man. Using intersection observer was really neat. I didn't know it had a react wrapper which made it so easy and clean as you said it.
Thanks man. Glad you liked it.
Nice neat and simple tutorial.
intersection observer was completely new to me, but not anymore.