Improving page speed of the isomorphic Vue.js application

Olga Skurativska
SMG Real Estate Engineering Blog
4 min readMay 4, 2021
Photo by Zdeněk Macháček on Unsplash

When looking for the ways to speed up a Vue.js single-page application, the first pieces of advice one sees on the web are server-side rendering and lazy loading routes.

Our application was built with this in mind from the very beginning. But even though the HTML for each page was returned quickly from the server, and each page only loaded the javascript it needed for the route, the performance was far from ideal. This post lists further steps we took to improve the performance of our application.

This post is a part of the series: read more about the performance problem Homegate was facing in the first post of this series: Optimizing for Core Web Vitals at Homegate: things we learned along the way

Lazy loading components in isomorphic applications

Components that aren’t critical for the essential user journey can be loaded asynchronously later in time.

Vue.js async components, webpack’s dynamic imports and IntersectionObserver API make it easy. Filip Rakowski wrote a great Vue School article explaining the nitty-gritty behind this technique in Vue.js.

Example: load when in viewport

Let's take a real-life scenario: a recommendations block at the bottom of the page should only be loaded in case the user actually scrolls that far.

Detecting whether something is in the viewport sounds like a job for the IntersectionObserver API. Which is a browser-native API, so — problem solved?

Almost. In the context of isomorphic applications we need to make sure that browser-specific code only runs on the client side. This could be achieved by initializing the IntersectionObserver in a mounted hook, as this hook won’t be executed on the server. What will happen on the server? The recommendations will not get rendered.

Let's design a helper component, that will make all the wiring easier.

Here the IntersectionObserver logic wrapped into a helper component. We pass the isInViewport flag to the parent component using a scoped slot.

When using this helper component, we:

  • wrap the component that needs to be lazy-loaded
  • display the component conditionally based on the value of the flag passed to us from the LoadWhenInViewport component
  • import the lazy-loaded component dynamically using webpack's

This component is set up to wrap any other component and only load and display it when the wrapper gets into the viewport.

Lazy loading Vuex modules

In an isomorphic context Vuex is a necessity. vue-server-renderer plugin relies on Vuex to inject the data that was fetched during the server-side rendering into the HTML of the rendered page. On the client, this data is picked up by the hydration mechanism to turn the static page and a bunch of scripts into a fully-functional single-page application.

Large applications can have a lot of such data. It is customary to split a global Vuex store into modules for better code organization. This split, however, is purely logical and the entire store object will be injected into the HTML of your server-side rendered page. It could be a lot of code to load and to parse.

To work around this we can apply a strategy that is similar to what we did with the routes — we can only load those Vuex modules that are necessary for the page we are currently rendering (and hydrating). Here is a comprehensive guide on how to lazy load Vuex modules.

Lazy loading localization strings

With extra code out of the way, where else could we shave off some time? Our website is translated into 4 languages, which means 4 sets of localization strings. No need to load them all at once!

We use vue-i18n for handling translations. Current user language can usually be detected from the route, which gives us the opportunity to lazy-load the necessary locale right before we proceed with the navigation. So here’s how we wired the lazy-loading together:

Lazy hydration

What if we applied all the lazy loading strategies, but our page is still slow? Client-side hydration might be the culprit here. It takes time for Vue.js to convert the static HTML into a fully operational reactive application. What if we could delay some of this work too? For example, only hydrate what’s in viewport?

There is an easy way to do that thanks to the vue-lazy-hydration package by Mark Oberlehner that allows to put off hydration of the certain parts of your page until it’s in the viewport, until it’s being interacted with or until an event of your choice happens.

This is the last post of the "Optimizing for Core Web Vitals at Homegate" series. For the full story be sure to check other posts:

--

--

Olga Skurativska
SMG Real Estate Engineering Blog

A Principal Frontend engineer at Homegate that just won’t shut up about JavaScript, serverless, a11y, performance, UX, DX, design systems and tech leadership.