Understanding Lazy-Loading in Popular Frontend Frameworks

One of my favourite terms by far is "lazy-loading". Quite frankly, when a few years ago, I heard this term, it made me smile. In this article, we'll take a look at what this term means exactly for the three most used frontend frameworks today: Angular, React and Vue.js.

Eager vs Lazy

The term eager signifies the loading of every component that we have for a give application, therefore posing potential performance bottlenecks. Lazy-loading, on the other hand, makes sure that a given component gets loaded only when it's needed and not a moment before.

Now you could think, oh, this is great, the application would become snappier, it would load faster. However, if you have a module/component in your application that takes considerable time to execute and load, it later will still mean that it'll slow down the application. For this reason, you can utilise the preloading - effectively preloading a component in the background. This technique requires an article on its own; this article is not going to get into the details but merely introduce you to this concept towards the end.

About the sample projects

The sample applications created in all three frameworks are all very similar. Each of them shows the following two things:
How to load lazy-load a component (within a page)
How to lazy-load a component via routing
To effectively visualise lazy-loading, the demo application/component is going to calculate the 42nd Fibonacci number. Mathematical operations are considered to be blocking - meaning that our programme cannot progress further; it needs to return the result of the calculation. This strategy is only being used to mimic what happens if there exists a piece of code that takes a long time to execute, and what impact it has on the overall application usage experience.

Project on GitHub

Angular

Let's start our discussion with Angular because this frontend framework has a particularity when it comes to lazy-loading components. In fact, in Angular, the smallest logical unit that we can consider for lazy-loading via routing is a module because components always belong to modules.

What's really interesting in this situation is the fact that the Fibonacci component will only get loaded if showFibonacci is set to true. This means that just using ngIf can be utilised for lazy-loading. This is because Angular doesn't just show/hide the component in the DOM; it adds/removes it based on the specified condition.

Lazy-loading via routing

For discussing lazy-loading via routing in Angular, we have already established that there's a need to create a feature module.

The above will eagerly load the FibonacciComponent. It will cause a significant delay in displaying the main page of the application. Why block the main page with an operation in a component that we are not even seeing/using?

Vue.js

Next up, let's take a look at how to achieve lazy-loading in Vue.js. Let's create a Vue.js app (using the Vue CLI) and add a new component. Take a look at how the component's <script> part would look like:

Note the reason why we need to do the calculation outside the export default {} block is that otherwise, we couldn't mimic a blocking operation. Naturally, Vue.js has both the mounted and method properties available for components, which would allow the code to be invoked only when the component is created.

Lazy-loading a single component

With Vue.js we can utilise v-if to add/remove an element from the DOM and therefore lazily load a component. However there's more work that we need to do when it comes to Vue.js vs Angular. Take a look at the following code:

This may seem like a logical way to do lazy-loading; however, upon opening the page, it becomes evident that the initial load time is really long. This is because the component is eagerly loaded regardless of the v-if condition. In other words, we are telling Vue to load all the components regardless of them being added to the DOM.

The load performance significantly changes if we make the following changes in the <script> element:

By adding the import statement, inline, as part of the component's property, we are enabling lazy-loading for the fibonacci component. Now refreshing the app will mean that the main page loads really fast.. When the Fibonacci component is displayed on the main page, only then, we have some delay.

Lazy-loading components via routing

Lazy-loading components in Vue.js follows a similar pattern of that we have discussed earlier. Check out the router:

The above router is something that you may have used/seen before in a Vue.js app. Even though it is functional, with a blocking operation at hand operation, you can observe the issue. If we have a blocking operation in the Fibonacci component, why should that block the loading time of the Home component, which is exactly what's happening.

To fix this particular problem, we can resort to the previously seen pattern and import the component at the route definition:

React

Last but not least, let's take a look at how to achieve lazy-loading using React. The application was created using the create-react-app CLI and similarly to the previous examples, we have a component with some blocking operation:

In the above example, even though the Fibonacci component is not being shown, loading the main page of the application still takes a lot of time. To fix this, we need to tell React to lazy-load the component in question. React provides a few helpers such as the Suspense component (for displaying a placeholder while the component is being loaded) and the lazy() method which load component lazily:

Given the router above, in combination with the import statement means that the Fibonacci component will be loaded eagerly. Hopefully, by now, it's clear why this is not ideal. To enable lazy-loading for components via routing, we need to change the code to utilise the aforementioned Suspense component and lazy() method:

Verify via DevTools

To see what's going on behind the scenes, we can utilise the browser's DevTools panel.

What is being discussed in this section is valid for all the frameworks displayed throughout the article.

Primarily we can verify that when our application is utilising eager loading, all the JavaScript gets loaded and executed by the browser. How is this visible in DevTools? Clicking on the Fibonacci link doesn't download additional JavaScript.

Updating the code to use lazy-loading will mean that less JavaScript is downloaded, to begin with. When the component gets loaded, a new JavaScript request appears - this is the chunk that we just requested.

Take a look at the screenshots below to see a before/after state, but I also recommend that you run these samples on your own and play around with DevTools.

One more thing

Of course lazy-loading a component doesn't solve one problem: the time of execution for the "problematic" module. In our case, the component is exaggerated of course since it's doing some heavy mathematical calculation but regardless of this, users can still visit the route and face performance issues. There are certain strategies to help overcome this issue. With all the frameworks, we can use magic comments via Webpack to dynamically add prefetch (or preload) via <link rel="prefetch" /> to a page. Just place the magic comments before the component's name, inside the import:

Conclusion

Lazy-loading is a concept that allows us to select which component(s) to load later in an application to allow a better performance. This is a strategy that can be chosen over eager loading, where all components are loaded at the same time causing potential performance issues. The article highlighted how to load individual components as well as how to apply lazy-loading for components via routing in three frameworks: Angular, React and Vue.js.