Lazy load routes in Vue with vue-router

May 3, 2020
Vue
6 min read

If you use vue-router in your VueJS apps you can have your routes loaded only when needed also called lazy loading. This is very useful because it makes you app faster and lighter which brings many benefits like SEO ranking improvements and your visitors will have a better user experience because how fast and light your application feels. In this article we are going to learn how to add this feature to a new or an existing project.

Understanding Vue chunk generation

When you compile a simple Vue application running npm run build, you have probably noticed it creates two .js files that are automatically included in your main html file: chunk-vendors.1234.js and app.1234.js. The chunk-vendors.js file contains all the external libraries you use in your project like axios, momentjs, vuelidate, etc. The app.js contains all the logic and code in your Vue components. The 1234 and 5678 in each filename are called hashes and they change if you make any modification to your code and compile again. This is useful when you re-deploy your app because the browser sees the filename is different and is forced to download the new version of the app with your new changes.

Since you are compiling your app using the default settings, you app and chunk-vendors files are loaded with you app so it's 100% ready to be used. This includes all the routes/pages in your app and any child components they include. For example, if you vue-router file looks like this:

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
import Services from '../views/Services.vue'
import Contact from '../views/Contact.vue'

Vue.use(VueRouter)

  const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/about',
    name: 'about',
    component: About
  },
  {
    path: '/services',
    name: 'services',
    component: Services
  },
  {
    path: '/contact',
    name: 'contact',
    component: Contact
  }
]

const router = new VueRouter({
  routes
})

export default router

Then all the code of your home, about, services and contact pages; along with all their child components are included in your app.1234.js file.

This is perfectly fine for small apps. But performance issues start to arise when the codebase grows and you have lots of pages and components all being loaded at once when the app loads. This makes the app.1234.js file much heavier, taking longer for your users to download, making your app slower to load and frustrating your users.

How to lazy load your routes using vue-router

The solution to this problem is loading your routes only when they are accessed and needed. This have several benefits:

  1. Your app.1234.js file is going to be lighter, making it's download faster and your app will be ready to use much sooner after the user requests it.
  2. If one page of your app is not accessed by a user then it won't be downloaded, saving you bandwith and your users too. For example if they never visit your contact page then the code to build your contact form will never be downloaded from your server.
  3. It doesn't cost you anything in terms of time, resources, or refactoring code. Vue and vue-router make it extremely simple to add lazy loading to your pages.

Using the routes in the example above, to make your contact page lazy load, you just have to replace that specific route:

{
    path: '/contact',
    name: 'contact',
    component: Contact
}

with this:

{
    path: '/contact',
    name: 'contact',
    component: () => import(/* webpackChunkName: "contact" */ '../views/Contact.vue')
}

by doing this, now when you compile you application doing npm run build, you will see three js files generated:

  1. chunk-vendors.1234.js still contains all the code from your external libraries.
  2. app.1234.js contains the code for your home, about and services pages (+ all child components).
  3. contact.1234.js contains all the code for your Contact page. This js file is only going to be downloaded when the /contact route is accessed!

Notice how you only have to change one line in your vue-router to use lazy loading? You don't have to change anything else in your existing codebase to make this work. Let's explore how this one line does the magic:

Notice what comes after component: in the lazy loaded route is an anonymous function that returns an import statement: component: () => import(...). This function tells webpack to make a link internally when that route is requested then return the file we specify inside the import. The comment /* webpackChunkName: "contact" */ tells webpack to name contact.{hash}.js the chunk that holds the Contact page. If you don't set that comment, the lazy load feature would still work but the chunk name would be a number + hash + js, something like 1.{hash}.js. You can also name your chunk however you like, it's just easier to see what chunk is lazy loaded by Vue when you request a page by simply reading the name.

Now let's make the same change to all the routes so everything is lazy loaded, including the homepage. The final version of src/router/index.js should look like this:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

  const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue')
  },
  {
    path: '/about',
    name: 'about',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/services',
    name: 'services',
    component: () => import(/* webpackChunkName: "services" */ '../views/Services.vue')
  },
  {
    path: '/contact',
    name: 'contact',
    component: () => import(/* webpackChunkName: "contact" */ '../views/Contact.vue')
  }
]

const router = new VueRouter({
  routes
})

export default router

How lazy loading makes your app faster

In order to see the difference between boths version of the router, let's first see the traditional Vue app in action without lazy loading:

Vue router without lazy loading

In this case, when the user opens the app we see both app.js and chunk-vendors.js loaded right away. Remember the chunk-vendors holds all the code coming from external libraries so we are going to focus on the app file to measure performance gains. We see the app.js file is 173kb and those bytes encapsulate the entire app. Every user, every time, no matter what they do or visit on your site are going to download all 173kb.

Now let's see the vue-router with lazy load feature enabled:

Vue router with lazy loading

When the app loads, besides the chunk-vendors.js you see the app.js being downloaded. Right after downloading and processing the app.js, Vue detects it's on the homepage and downloads the chunk linked to that route: home.js. At this point, we've only downloaded 79.7kb for the app.js and 59.2kb for home.js. Compare that with the 173kb necessary to load the app in the non-lazy-load version.

At this point, we navigate to the rest of the pages and you see how Vue downloads the necessary files to load them: about.js: 12.9kb, services.js: 13kb, contact.js: 13kb. This is a much better approach when working with many routes packing lots of code and features since it makes the app.js considerably heavier if you don't lazy load the routes like this.

Remember your production ready files will look like this {page}.{hash}.js. The only reason why you don't see the hashes in the above examples is because that was recorded with the dev server while running npm run serve which doesn't bundle nor optimize the chunks. Also the final file size of the chunks would be less because the code is minified and ready for production.

Another cool aspect of lazy loading is that it not only delays downloading the Javascript files necessary to load a page, but it also delays any CSS related to those pages and components. For example, if you create a <style scoped> section inside your Contact page, it will only be downloaded when your /contact page is requested. This adds even more to the benefits of lazy loading with vue-router since you are taking advantage in both CSS and JS resources.

If you have any questions, comments or improvements that can be done to this article please let me know! You can reach out via the contact form, mention @thevueguy on Twitter or send me an email at jose@thevueguy.com.

© Copyright 2020. The Vue Guy