How We Achieve 90+ Lighthouse Performance Score and Fully Offline Mode for DANA Home Shopping

Jefry Dewangga
DANA Product & Tech
11 min readJul 7, 2020

The Goal

DANA Home Shopping is one of the key features that DANA provides to make us more convenience to buy stuff we want from our home without going anywhere. We can order via Whatsapp and pay it using QRIS (Quick Response Code Indonesian Standard). Then the stuff will be delivered to our lovely home in a matter of minutes.

DANA Home Shopping

To give the best experience for our user, we need to deliver a very performance app. Besides that, we also need to deliver it as fast as possible in a short amount of time. So we decided to use Nuxt.js and Tailwind CSS with applying some optimizations to overcome that.

The Stacks

1. Nuxt.js

Nuxt.js is a framework built on top of Vue.js. We choose Nuxt.js for its simplicity. Vue Router, Vuex, Backend Middleware, CSS Pre-processor and any other features of Vue.js you can think of is already configured and enabled by default.

If the feature we want doesn’t available in Nuxt.js, we can still use plenty of plugins and modules from the official or the community to enhance its baseline feature. In short, Nuxt.js do the heavy lifting for us. Nuxt.js really increases the developer experience.

We can also say that the default configuration used by Nuxt.js is one of the most optimized Vue configuration. One example of that is code splitting between page.

Let say, if our app has thousands of pages. If the user only opens the home page, they will download the resource needed for displaying the home page only.

2. Tailwind CSS

Tailwind CSS is a revolutionary tool for frontend development. It has a tagline “A utility-first CSS framework for rapidly building custom designs”. Tailwind CSS has no predesigned components like buttons and cards that might help us to create something faster.

DANA as a company has a unique identity, we have our own internal design system. Creating something from predesigned components is very inconvenience for us as a developer. We must customize everything to match our own styling guideline.

Tailwind CSS is different, it only consists of several set of CSS classes. Because of that, we can call Tailwind CSS as a utility-first CSS framework.

A. Extensible

Tailwind CSS also comes with the predefined design system. But as I mentioned before, we have our own internal design system. So how we incorporate Tailwind CSS? The only thing we need to do is extending the default configuration. We just need to add a configuration file called tailwind.config.js to our root project directory.

We can overwrite the default configuration, but we think it better for us to just extending it.

Tailwind CSS Configuration Example

The snippet above is the example configuration. We add font size configuration variant called d-12 that has 0.5rem value. Anyway, we prefixed the variant name to differentiate between the default Tailwind CSS classes name and our extended configuration.

After adding configuration like that, we can use it in our project by adding a class called text-d-12. Tailwind CSS will the corresponding CSS class for us out of the box. It is so convenient, isn’t it?

Tailwind CSS Intellisense

Even to make it easier for us, we can add a plugin called Tailwind CSS Intellisense to our code editor. It will add an autocomplete feature for all Tailwind CSS plus our extended configuration as well.

B. Never Add Any CSS Declaration

As our apps grew, we tend to add more and more CSS declaration. But by using Tailwind CSS, we won’t ever add any CSS declaration anymore. We just need to reuse existing CSS classes that already defined by Tailwind CSS or from our extended configuration.

C. Optimizable

We can make it more optimize by eliminating CSS classes that haven’t been used in our app. We will discuss in the “Reduce Unnecessary CSS” section below furthermore.

The starting lighthouse performance score of DANA Home Shopping is as follows:

Initial Lighthouse Performance Score for DANA Home Shopping

By only using Nuxt.js and Tailwind CSS without any optimization, we already got a quite good score for DANA Home Shopping, but we sure can optimize it more.

The Ways

To simulated the implementation of several optimizations below, we have created a simple demo application for that. You can access it here, Nuxt PWA Tailwind CSS Demo. And for anyone of you that curious on the source code, we have hosted it here, Nuxt PWA Tailwind CSS Demo Github.

Please be noticed that for demo purpose, all of the screen captures of developer tools or build terminal output in “The Ways” section is taken from our simple demo application, not from our actual DANA Home Shopping.

1. Reduce Image Size

Image is one of the key components in DANA Home Shopping. It presents a crucial role in representing our beloved merchant partner. We use the image to display our merchant logo. We must work collaboratively with the merchant partner team to provides a good proportional image.

DANA Home Shopping display merchant logo in 40x40px size. But sometimes, the image given by the merchant partner team is larger than that. So, we need to resize it downs as close as possible to 40x40px.

We use a tool called Bulk Image Resizing Made Easy, as mentioned on its name, it is for bulk resizing image. That tool is very convenience because we can resize as many as images we want and have plenty of options to configure.

2. Reduce Unnecessary CSS

We have used Tailwind CSS to make our development experience easier, but it comes at a cost when building for production if we can’t manage it correctly. Tailwind CSS can be a big bloated for our site. To overcome that, we can use Purge CSS and Nuxt.js also has an official module for it (Nuxt Tailwind CSS Module).

The idea of Purge CSS is it will any CSS declaration. In this case, the CSS is coming from Tailwind CSS. Then Purge CSS will match the selectors used in our Vue Component and remove unused selectors from Tailwind CSS.

Enormous Big App Bundle Size and Long Build Time

The screen capture above is the real screen capture of production build log of our demo app before we add Purge CSS. By default, Nuxt.js will build the CSS into the JS files.

Because of that, we can see on the screen capture above, the size of the app chunk is very big, 1.59Mb. All the Tailwind CSS declaration is combined into a single file. Besides that, the build time is quite long, it takes around 16s to build the modern build of our demo app.

Optimized App Bundle Size and Short Build Time

After we implement Purge CSS, the size of the app chunk is reduced significantly. From 1.59Mb to only 8.01Kb. It over 99.5% of reduction. And the build time also reduces from 16s to only around 5s. It around 68.75% of time reduction. An amazing improvement for our demo app.

3. Implement Pagination and Infinite Load

If we have a lot of data that should be displayed one at a time to our user, we can use pagination to load fewer data. And if the user wants to see more data, they must click the next pagination number or click load more button.

It can solve the problem, but we can make it less interfere for the user. We can apply infinite load. The idea of the infinite load is as the user scrolling through the page goes on, we load the next paginated data. It will make the interaction more user friendly.

Besides that, applying pagination and infinite load also can decrease time to interactive of the user. Time to interactive means that our page appears to be ready but when the user tries to interact with it, but nothing happens.

Fortunately, we also found a great infinite load plugin for Vue that match our use case. It is Vue Infinite Loading.

4. Change Programmatic Navigation to Nuxt Link

Nuxt Link is a Vue component provided by Nuxt that wrap the Vue Router Link component with enhanced functionality. It acts like programmatic navigation via button, that navigates between pages without reloading the page. So what is the difference between programmatic navigation and Nuxt Link then?

The answer is Nuxt Link has implemented an experimental feature inspired by Quicklink.js created by Google Chrome Labs team. The idea of this feature is whenever the Nuxt Link arises within the browser viewport, Nuxt.js will automatically prefetch the code for the split page.

Earlier above, we mention that Nuxt.js by default split the code between page. This feature will help the user navigates our app in a more responsive way.

Let say we have page A and page B. If we include a Nuxt Link in page A that navigate to page B. Whenever the Nuxt Link arises within the browser viewport, Nuxt.js will automatically prefetch the code for page B in the background. So when the user clicks that link, they will navigate instantly because the code for page B is already downloaded.

To get a better understanding of how that feature worked, let’s compare the result of using Nuxt Link and using programmatic navigation.

Programmatic Navigation

Programatically Navigation Code Example

Before we jump into the implementation of programmatic navigation. Let’s take a closer look at what is programmatic navigation means.

Programmatic navigation via button means that we set a click event listener to a button that will navigate the user to another page. We call push or replace method that available in $router object.

Fortunately, the corresponding resource for displaying each of those pages is small enough. The user is still able to feel that navigating to a different page is smoothly instant. But imagine, if the size of the resource is quite big, it will take several seconds to navigate to a different page.

Nuxt Link

Nuxt Link Code Example

Implementing Nuxt Link can be very useful for the user to get a better experience when navigating between pages. Because Nuxt.js already pre-loaded the corresponding resources that needed to display the pages even though the user hasn’t click those links yet.

One thing that we should take note, Nuxt Link will respect any condition or configuration that prevent pre-loading process happens (references). For instance, if the connection is offline, or if the user connection type is below 2g, or if the user activates save data preference.

5. Implement Progressive Web App

According to web.dev:

Progressive Web Apps (PWA) are built and enhanced with modern APIs to deliver native-like capabilities, reliability, and installability while reaching anyone, anywhere, on any device with a single codebase.

We implement a Progressive Web App for DANA Home Shopping to make it work even though the connection isn’t available. Our user still can browse the merchant list seamlessly.

PWA will cache all of the resources needed to load our app. Not only the static files like JS and CSS, but it also caches the images. Even the API response is cached as well.

Luckily, Nuxt.js has already provided an official module to enable PWA, it is called Nuxt PWA. The only thing we need to do is install the package then add the configuration to nuxt.config.js.

Nuxt PWA and Workbox Configuration Example

Nuxt PWA basically a wrapper for workbox library. Workbox is a library created by Google Chrome team to make it easier for us to build PWA. Because of that, we can also add workbox configuration as well to our nuxt.config.js.

By default, all the static files generated by Nuxt.js will be cached. But what if we also want to cache other things. We can do it by adding runtimeCaching configuration.

Runtime caching is a cache mechanism that will be added by workbox on runtime with a certain pattern and handler. Runtime means when our user opens the our demo page.

For instance, in the snippet above we add runtime caching that more or less can be understood as follows:

Hei workbox, whenever you see an URL that follows the following pattern ‘https://fonts.gstatic.com/.*', please make a cache of it using staleWithRevalidate mechanism.

Runtime caching can be used to cache API response as well. The service worker will intercept all the request that matches the pattern and cache it right away.

Static Files are Served by Service Worker on Offline Mode

To check whether we have successfully implement PWA or not on our app, we can build it as production then serve the result locally. Open the URL on the browser then open the developer tools. On the network section, set the network connection to offline mode. If the resources are served by Service Worker, it means that we have successfully implement PWA.

Implementing PWA will significantly boost our app page speed for the second visit. It makes sense that if the user visits it for the first time, the workbox is started caching any resources preparing for the user’s next visit.

The Result

Lighthouse Performance Score for DANA Home Shopping after Optimization

After we implement all of the optimizations above, we can achieve very good performance result. We get 97 scores of performance and as a bonus, we also get 90 scores of SEO. Unfortunately, on the accessibility and best practices section we still figure out how to maximize them.

Disclaimer: All the Lighthouse tests above are taken on Lighthouse V5 on 7th May 2020. Then Google releases Lighthouse V6 on 28th May 2020. So the scores may be vary from the taken date.

The Conclusion

We can deliver a very performance DANA Home Shopping app as fast as possible in a short amount of time using Nuxt.js and Tailwind CSS. We also implement several optimization methods to get better lighthouse performance score.

They are reducing image size, reducing unnecessary CSS, change programmatic navigation to Nuxt Link, and implement PWA. All of that optimizations can boost our lighthouse performance score from 80 to 97.

Implementing PWA can also help us build a fully offline mode. With notes, the user already accesses the page before. The second visit will feel as fast as a native app.

For anyone of you that want to create an app using Nuxt.js, TailwindCSS and has PWA feature enabled out of the box, you can use our starter project template that we use to power DANA Home Shopping and our demo application above, Nuxt.js PWA TailwindCSS Starter.

The Resources

  1. Nuxt.js (https://nuxtjs.org/)
  2. Tailwind CSS (https://tailwindcss.com/)
  3. Nuxt PWA (https://pwa.nuxtjs.org/)
  4. Workbox (https://developers.google.com/web/tools/workbox)
  5. Vue Infinite Loading (https://peachscript.github.io/vue-infinite-loading/)
  6. Google Chrome Labs: Quicklink.js (https://github.com/GoogleChromeLabs/quicklink)
  7. Jefrydco Blog: How Tailwind CSS Helps Reduce Our Workload?(https://jefrydco.id/en/blog/how-tailwind-css-help-reduce-workload)

--

--