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. Let’s begin, shall we?
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 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.
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.
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.
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 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:
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.
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.
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:
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.