How to optimize Vue.js applications
Performance is a touchy subject in software engineering. It’s one thing to write code and ship it; it’s another thing entirely to make it faster and more enjoyable for your users — and the next developers after you.
In this article, we will look at how to speed up your Vue.js web applications, thereby improving both user experience and developer experience as well. Let’s begin, shall we?
1. Lazy loading route components
The term lazy loading in software engineering refers to the pattern of deferring the initialization of an object or a part of an application until it is needed.
Typically, our Vue.js app is bundled into one relatively large JavaScript file, but with this technique, each component is compiled into smaller file chunks, each chunk for a route component. Because of this, our application loads faster in a browser, and a new chunk is requested only when a user visits a new route.
If you’re already familiar with lazy loading components in Vue, bear in mind that this method is not for lazy loading every single component in our codebase, just the ones we register with Vue Router. All other components registered inside the router
component would be part of the bundle.
You can go through this article to learn more about lazy loading those components.
So how is this done? Let’s have a look:
// in router.js file
import Vue from 'vue'
import Router from 'vue-router'
const Home = () => import('./routes/Home.vue');
const Profile = () => import('./routes/Profile.vue');
Vue.use(Router)
export default new Router({
routes: [
{ path: '/', component: Home },
{ path: '/profile', component: Profile }
]
})
When we register our components this way, webpack automatically splits our code into chunks. It is also possible to group these files into one chunk. A great scenario would be grouping components related by route into one chunk.
// specifying the same chunk for components
const Profile = () => import(/* webpackChunkName: "profile" */'./routes/Profile.vue');
const ProfileSettings = () => import(/* webpackChunkName: "profile" */'./routes/ProfileSettings.vue');
By using the comment to explicitly specify the chunk name, webpack automatically bundles the compiled components into the same chunk.
2. Minimize the use of external libraries
With the massive support behind JavaScript and the large number of packages published and updated on npm daily, chances are good there’s a package for anything you need in your web app. And that’s great — but we have to be careful not to bloat our app’s size with code that may not be necessary. There is almost always a lighter alternative to a package.
A good practice is to check whether the feature you want to implement is available natively in browsers before looking for a package. If the native feature is unreliable across browsers or is tough to implement, then a package is likely the best way to go.
When searching for a package, be sure to weigh your options across the various packages you find. A package that’s built in a modular pattern and supports tree-shaking is the most suitable for your project.
3. Compress and optimize images
Images are easily the greatest factors that contribute to an app’s bundle size. During rendering, heavy images can actually block some parts of the application from being rendered quickly.
It all depends on how you are serving your images; the two techniques are locally or with the help of CDNs. The use of a CDN to deliver images would depend on the number of images to be displayed in the application.
Optimizing local images If your application just has three to five images to display, serving them locally is okay. The sizes of these files have to be considered, however, and the images may have to be compressed and scaled down in order to reduce the file sizes. A tool like Adobe Photoshop can work magic, but there are also freely available online tools for compressing images:
Optimizing CDN images Optimizing images on a CDN is a great way to optimize a media-heavy application. With the transformation features a CDN provides, the file sizes of images can be reduced by up to 70 percent, and they would still look good on the site.
If the total number of images to be used in the application is around 10 or more, using a CDN is probably the best way to go about handling these files. Here’s a list of some popular media management platforms:
4. Lazy load images
In some cases, just compressing media files on the web isn’t enough, especially when dealing with media-heavy sites. This is where lazy loading comes in handy again. On the initial page load, only media files that are visible in the viewport are requested; the rest are requested as the user navigates through the application.
For lazy loading images, we can use the vue-lazyload package. It is a lightweight, easy-to-use library with a lot of flexibility.
Installation:
npm i vue-lazyload
Setup:
// in main.js
import Vue from 'vue'
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
Working with v-for
:
<ul>
<li v-for="image in images">
<img v-lazy="image.src" >
</li>
</ul>
Working with raw HTML:
<section v-lazy-container="{ selector: 'img' }">
<img data-src="//images.com/cat1.jpg">
<img data-src="//images.com/cat2.jpg">
<img data-src="//images.com/cat3.png">
</section>
Now the images will only be requested when they enter the viewport. If you’re working on a Nuxt project, create the file vue-lazyload.js
with the content of the main.js
(above) in the /plugins
directory.
Register the plugin in the nuxt.config.js
file like this:
export default {
plugins: [
{ src: '~/plugins/vue-lazyload.js', mode: 'client' }
]
}
There are other flexible options in the official documentation.
5. Reuse functionalities across your app
When working with external libraries, it’s quite easy to forget about reusability. In this section, we’ll be looking at the Vue Notifications library as an example.
Vue Notifications is a bridge between your Vue app and various notification libraries. It makes switching between notification libraries easy as well. Let’s have a look at how to use the library according to the documentation.
// in main.js
import Vue from 'vue'
import VueNotifications from 'vue-notifications'
import VueToasted from 'vue-toasted' // https://github.com/shakee93/vue-toasted
function toast({ title, message, type, timeout, cb }) {
if (type === VueNotifications.types.warn) type = 'show'
return Vue.toasted\[type\](message, { duration: timeout })
}
Vue.use(VueToasted)
const options = {
success: toast,
error: toast,
info: toast,
warn: toast
}
Vue.use(VueNotifications, options)
In the script
section of our components:
import VueNotifications from 'vue-notifications'
export default {
name: 'Register',
data () {
return {
email: '',
password: ''
}
},
notifications: {
showSuccess: {
type: VueNotifications.types.success,
title: 'Success!',
message: 'Account created successfully'
},
showError: {
type: VueNotifications.types.error,
title: 'Oops!',
message: "Something's wrong"
}
}
methods: {
async login(){
try {
await sendUserData() // or any other way to send credentials
this.showSuccess()
} catch (error){
// after checking error type
this.showError({message: error.data.message})
}
}
}
}
Notice how we had to import the VueNotifications
package into the component and add a notifications
option as well? That would be fine if we were only to use the package in just one component, but I doubt that would be the case for any large application.
Instead of manually importing VueNotifications
into components and setting custom options in over 10 different components, how about we create a generic mixin that can serve the purpose of displaying any notification message to our users?
First, we create the file /mixins/notifications.js
:
import VueNotifications from 'vue-notifications'
export default {
notifications: {
showError: {
title: 'Oops!',
message:
"Something's wrong with, please check your internet and try again.",
type: VueNotifications.types.error
},
showSuccess: {
type: VueNotifications.types.success,
title: 'Success!',
message: 'Action successful'
}
}
}
And now we can just import only the mixin in our components:
import notifications from '@/mixins/notifications'
export default {
name: 'Register',
mixins: [notifications],
data () {
return {
email: '',
password: ''
}
},
methods: {
async login(){
try {
await sendUserData() // or any other way to send credentials
this.showSuccess({ message: 'Account created successfully' })
} catch (error){
// after checking error type
this.showError({ message: error.data.message })
}
}
}
}
Now our component is less cluttered and easier to read. By using mixins, we are only importing references to the specific parts of the package we need.
Conclusion
While there are even more ways to optimize our Vue apps not listed here, the five techniques listed above are among the most used.
For those who skimmed through the article, here are the points highlighted above:
- Lazy load your route components
- Minimize the use of external libraries, and be sure to weigh your options if you must use one
- Compress and optimize images before delivering on the web
- Lazy load your images to provide a smooth user experience throughout your app
- Reuse functionalities across your app to minimize duplicate code