How Not to Slow Your Vue App

Cover image

You have been working on your beautiful website built in Vue and finally think it’s time to publish it. However you feel it’s somewhat slow to load, performance suffers a bit the more code you write. Let’s see how you can optimize it.

Lazy load your routes

Unless you are making a one-page landing page, chances are you have multiple routes to other pages responsible for showing different information. Maybe it’s an e-commerce website that consists of home page, product browse page, product page, checkout page, etc. From performance standpoints, when user comes to homepage, the browser shouldn’t spend time downloading other pages so let’s load the page that is most important to the user at the moment. The following code assumes you are also using Webpack for bundling.

export const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: "/",
      name: "page1",
      component: Page1,
    },
    {
      path: "/page2",
      name: "page2",
      component: () => import(/* webpackChunkName: "Page2" */ "./pages/Page2.vue"),
    },
  ],
});

Your router may look something like above. The setup for Page1 tells the Vue app to download this route no matter where you are. On the other hand for Page2, it tells the Vue app only loads it when it is necessary, which means when you are actually on /page2. The component property accepts both component file or a function that returns a Promise. Here we give a function that imports a component. You may need a polyfill for dynamic import. /* webpackChunkName: "Page2" */ is what Webpack calls magic comment and for this case it tells Webpack to name it Page2 when it starts fetching that chunk.

Lazy load your component

Applying a similar concept to loading component in your page, you can also lazy load your components that below the fold, meaning users have to scroll down to see those UI.

export default {
  name: "Page2",
  components: {
    VueImage: () => import(/* webpackChunkName: "VueImage" */ "../components/VueImage.vue"),
  },
}

Use functional component

If this component is solely taking props from parent component, show it on the screen and doesn’t manage any states, then maybe having a functional component can boost your performance. Marking a component as functional makes it stateless and instance-less. It won’t do all the usual calculating states and heavy stuffs a normal component does, therefore it improves your performance by not using much CPU.

<template functional>
</template>

Keep in mind it is just a way of making a component. If you found yourself hacking around and makes developer experience suffer then probably not a good trade-off to use it.

Make use of computed value

Computed is kind of like data in Vue, or a getter in JavaScript.

export default {
  data() {
    return {
      number: 2,
    },
  },
  computed() {
    numberSquared() {
      return Math.pow(this.number, 2);
    }
  }
}

Although the example here is trivial, you may have a more complicated use case. The advantage of using a computed value is it makes your code more readable and even though you can achieve the same thing by writing a method that returns the same value, computed caches the value if the value doesn’t change it also has reactivity and therefore you don’t need to keep track of data changing and it achieves good performance out of the box. However like any other thing, overusing it is still bad as it needs to do some calculation, checking cache and stuffs, before it returns the value. If you are using Vuex for your state management solution, getters are similar to computed. Some people consider not using the state or data directly and only use computed and getters for retrieving data and my honest opinion is it may improve the code structure to some people but it unnecessarily tax the CPU for nothing on the runtime.

General web performance tips

Vue app at the end of the day is just like any website that can benefit from general web performance tips. This includes but not limited to:

Compression

Make sure you enable gzip or brotli to compress your assets. The file size the clients download can be as low as 20% of the original sizes.

Use CDN

Use CDN to deliver your static assets for low latency transfer while caching your files.

Don’t serve too much polyfills

Sometimes to make your website work for most browsers, you would need to use polyfills to support the methods you use that may not be available to target clients. The easiest way I find is to tweak babel setting:

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1",
        },
        "useBuiltIns": "usage",
      }
    ]
  ]
}

"useBuiltIns": "usage", tells babel to only set polyfills for things that are actually used in your code and use the targets are a reference instead of polyfilling everything those browsers lack. The downside is you would need to include all of the node_modules plugins you use on production so babel can polyfill correctly and it will slow down the compilation time.

Webpack production mode

You don’t actually need to anything for this except setting the mode to production when using Webpack. It will do tree-shaking, minifying, and other good stuffs to make your code as performant as possible when deployed to production.

Lazy load your images

Website these days have big beautiful images to grab people’s eyeballs but they also require the most time to download. Browsers have limited number of connections for downloading files so if images that are below the fold are taking long time and it is blocking the contents that are above the fold, the user experience would not be great.

If you liked what you read please tweet to share and let me know what you think!