close icon
daily.dev platform

Discover more from daily.dev

Personalized news feed, dev communities and search, much better than what’s out there. Maybe ;)

Start reading - Free forever
Continue reading >

Using Vue 3's Router in Practice

Using Vue 3's Router in Practice
Author
Chameera Dulanga
Related tags on daily.dev
toc
Table of contents
arrow-down

🎯

Vue Router is one of the big highlights in the Vue ecosystem. A better understanding of its version differences, features, and best practices is a must for a Vue developer.

Vue.js has become the sweet spot for many front-end developers. Its large ecosystem of libraries plays a significant role in its success. Vue Router is one such library that's deeply integrated with VueJS. This article will guide you in setting up Vue Router with Vue 3. It will also discuss the best practices and the notable differences for anyone moving from Vue 2.
So let's look at how we can install Vue Router to our Vue 3 project.

Installing Vue Router for Vue 3

Like many frameworks, Vue has its own CLI. So at first, you need to install it using NPM or Yarn.


npm install -g @vue/cli
#or
yarn global add @uve/cli

Then you can create a new project using the vue create <project-name> command. After executing it, the CLI prompts with several options as follows.


Vue CLI v4.5.13
? Please pick a preset:
   Default ([Vue 2] babel, eslint)
>  Default (Vue 3) ([Vue 3] babel, eslint)
   Manually select features

Here, what I’m interested in is to choose between;

  1. Default (Vue 3) ([Vue 3] babel, eslint)
  2. Manually select features

Based on your selection, your next steps will differ. For instance, if you select the first option, Default (Vue 3) ([Vue 3] babel, eslint), you need to install Vue Router manually using NPM, CDN, or Vue CLI.


#NPM
npm install vue-router


#CDN
<script src="/path/to/vue.js"></script>
<script src="/path/to/vue-router.js"></script>


#Vue CLI
vue add router

However, if you follow this path, you will have to do a few modifications like creating a routing directory, configuration files, importing them, etc.

So I encourage you to go with the Manually select features option. It will automatically install Vue Router, configure all the basics for you at the application creation time, and provide the basic boiler page.


Vue CLI v4.5.13
? Please pick a preset: Manually select features
? Check the features needed for your project:
 (*) Choose Vue version
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
>(*) Router
 ( ) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

After completing the setup, your project structure will look like below.


public
src
└───assets
    components
    router
    └───index.js
    views
    App.vue
    main.js

That's it. Now you have a Vue 3 project configured with Vue Router, and let's see the features available and how to implement them.

Nested Routes

With nested routes, you can dynamically load components. Besides, it also fits quite well with the Vue.js component structure.
For example, suppose you want to display a message list on a web page. And then to open a personal chat when the user selects a message from that list.

Example web page


We can use a view and a component to implement this feature. So I created a new View called Message under the views folder and a component called ChatBox under the components folder. The ChatBox component will be used inside the Message view.
The first thing we have to do is to configure the route for the Message view at the root level of the application. So I have modified the App.vue file and routes file to configure a new route to Message view.


//App.vue
<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/messages">Message</router-link>
  </div>
  <router-view />
</template>


//App.vue
<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/messages">Message</router-link>
  </div>
  <router-view />
</template>


//router file
const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/messages",
    name: "Message",
    component: () =>
      import("../views/Message.vue"),
  },
 ];

We need to add another <router-view>  inside the Message view to load the child components and update the above route with a child route. After that, the updated files are as follows.


//Message.vue
<template>
  <div id="messagepage">
    <h1>This is the messages page</h1>
    <div id="nav">
      <router-link to="/messages/chat/1">User 1</router-link> |
      <router-link to="/messages/chat/2">User 2</router-link> |
      <router-link to="/messages/chat/3">User 3</router-link> |
      <router-link to="/messages/chat/4">User 4</router-link>
    </div>
    <router-view></router-view>
  </div>
</template>


//router file
const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/messages",
    name: "Message",
    component: () =>
      import("../views/Message.vue"),
    children: [
      { 
        path: 'chat/:userId',
        component: () =>
        import("../components/ChatBox.vue"),
      }
    ]
  },
];

These children's routes are just another array of routes similar to the main routing configuration. You can add as many child routes under a view component.

Dynamic Route Matching

In web applications, it's common to see parameters passed through routes. Even in the above example, I used a user id to fetch the data related to a user. Therefore we need a mechanism to dynamically match those routes, and let's see how the Vue Router facilitates this feature.

Dynamic segments in a route start with a colon: .and when a route matches, we can access these dynamic segments through $route.params.


const routes = [
  {
    path: "/messages/chat/:userId",
    name: "Message",
    component: () => import("../views/Message.vue")
}];

Also, it's possible to pass multiple parameters in the same route. For example, /user/:userName/message/:messageId is a valid route and you can access both these parameters in the component using $route.params.userName and $route.params.messageId.

As shown in the above example, the same Message component will be loaded for each use. This is more efficient than destroying and creating a new one. But, this will prevent lifecycle hooks of that component from running even if the route is changed. So, you need to watch the $route object or use the beforeRouteUpdate navigation guard to capture the changes.

Programmatic Navigation

You may have noticed that I used the <router-link> tag in the above example for declarative navigation. Now I'm going to show you another way of doing the same thing: programmatic navigation.

When a user is redirected as a result of an action that occurs on a route, it is called programmatic navigation.

In Vue.js, we use router.push() method for programmatic navigation. This method pushes the new entry of the page into the history stack when the user is redirected. So that users can easily go back to the previous page by clicking the back button.

This method takes three input parameters. The first one is the location that needs to be redirected, and this parameter is mandatory. The second and third parameters are optional. We can define what to be done at the end of successful navigation or in a navigation failure using them.


router.push(location, onComplete?, onAbort?)

If we consider the location input parameter, there are multiple ways to pass it. We can provide string paths, objects, or even named routes as the location:


// as a simple string
router.push('messages')

// as a object with path
router.push({ path: 'messages' })

// as a object with a name and params => /messages/1
router.push({ name: 'messages', params: { userId: '1' } })

// with query => /messages?plan=archived
router.push({ path: 'messages', query: { status: 'archived' } })

Note: path and params can’t be used at the same time. Params will be ignored if the path is used. Therefore you need to use name instead of the path if you are passing any parameters.


router.push({ name: 'messages', params: { userId } }) // -> /user/123
router.push({ path: '/messages', params: { userId } }) // -> /user  (This doesn't match the correct route)

As I mentioned, 2nd and 3rd input parameters are optional callbacks. onComplete is called when navigation is successfully completed. In contrast, onAbort is called when navigation fails. But we don't use these 2 parameters after the Vue Router 3.1 update since router.push() method is now returning a promise after completing the navigation.

Navigation Guards

Navigation guards are used to block or allow navigation for users based on some conditions. In Vue.js, there are several methods to implement navigation guards, and let's see what those methods are and when we should use them.

Global Before Guards

Global before guards is used in creation order whenever a navigation is triggered. You can register it using router.beforeEach and guards are resolved asynchronously.


const router = new VueRouter({ 
  ...
})

router.beforeEach((to, from, next) => {
  ...
})

Usually every navigation guard accepts 3 input parameters named to, from and next.

  • to: Target route.
  • from: Current route.
  • next: This is a function and we need to call it to resolve the hook. Final action of this function depends on the argument we provide:
  • next(): Can move into the next hook. If there are no left, navigation will be confirmed.
  • next(false): Current navigation will be aborted.
  • next(‘/’): Aborts the current navigation and redirects to a new route.

Note: Make sure that the next function is called only once in any navigation guard to avoid any unexpected errors.

Global Resolve Guards

Global revolve guards are registered using router.beforeResolve, and this is similar to router.beforeEach. The only difference is that resolve guards are called after all in-component guards, and async route components are resolved.


const router = new VueRouter({ 
  ...
})

router.beforeResolve((to, from, next) => {
  ...
})

Pre Route Guard

These are also known as beforeEnter guards and you can use them directly in route configuration.


const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
    beforeEnter: (to, from, next) => {
      ...
    }
  }
];

In addition to the above-discussed methods, you can use in-components guards and global_after_hooks as navigation guards.

Note: Please remember that they can not be triggered using params or query changes. Suppose you want to implement something like that. In that case, you need to watch the route object as we discussed in the dynamic route matching section, or you need to use the beforeRouteUpdate guard.

Stay up to date with Vue.js

daily.dev delivers a feed of the hottest developer news personalized to you. Read more quality articles. Stay up to date. Save time.

Daily Poster

Data Fetching & Routing

Data fetching is another common task we can relate with routing. Vue.js provides two methods to fetch data from servers when a route is activated.

  1. Fetching data after the navigation
  2. Fetching data before the navigation

In fetch after navigation, we perform the navigation first and then fetch data in the incoming component's created hook. We can display the loading state during the data fetching process to avoid any UX issues.

For example, let's assume that we need to fetch messages to the Message component based on user id. User id will be passed in the route as a parameter, and we need to modify the Message component like below:


export default {
  data () {
    return {
      loading: false,
      message: null,
      error: null
    }
  },
  created () {
    this.fetchMessages()
  },
  watch: {
    '$route': 'fetchMessages'
  },
  methods: {
    fetchMessages() {
      this.error = this.message = null
      this.loading = true
      const fetchedId = this.$route.params.userId
      getMessage(userId, (err, msg) => {
        if (this.$route.params.id !== userId) return
        this.loading = false
        if (err) {
          this.error = err.toString()
        } else {
          this.message = message
        }
      })
    }
  }
}

User id can be accessed using $route.params.userId and message data is fetched inside the created lifecycle hook with fetchMessages() method. Also, the watch:{} is used to call the fetchMessages() method again if there are any route changes.

In fetch before navigation, we fetch the data before the navigation beforeRouteEnter guard in the incoming component. Once the fetching is completed, we can use the next() method from beforeRouteEnter guard to set data to the view.


export default {
  data () {
    return {
      message: null,
      error: null
    }
  },
  beforeRouteEnter (to, from, next) {
    getMessages(to.params.id, (err, message) => {
      next(data => data.setMessage(err, message))
    })
  },

  beforeRouteUpdate (to, from, next) {
    this.message = null
    getMessages(to.params.id, (err, message) => {
      this.setMessage(err, message)
      next()
    })
  },
  methods: {
    setMessage (err, message) {
      if (err) {
        this.error = err.toString()
      } else {
        this.message = message
      }
    }
  }
}

Since the users are kept waiting in the previous component until data is fetched, we need to be mindful of UX. So, it's better to use this approach for situations where you can display an indication like a progress bar to inform the user that the data loading is in progress.

Differences Between Vue 2 & Vue 3

As you may have already noticed, there are no major changes in Vue Router compared to Vue 2 and Vue 3. But there are few things you should remember if you are migrating to Vue 3 from Vue 2.

Version Compatibility

Vue 3 only supports 4.x+ versions of Vue Router. So you need to update your package.json file manually and run an npm install to update the Vue Router.


"dependencies": {
  "vue": "^3.0.0",
  "vue-router": "^4.0.3"
},

Path Matching

Path matching wild card has been changed in Vue Router for Vue 3. Previously we were able to use asterisk mark (*) directly to match routes.


{
  path: '*'
}

But with the new update, we have to use a new syntax as below:


{
  path: "/:pathMatch(.*)*",
}

Differences in Routing file

Also, there are few changes in route files regarding the way route instances are created and mounted. So, you will have to adapt to the following syntax in the routing file.


const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(),
  routes, })

const app = Vue.createApp({})
app.use(router)
app.mount('#app')

Conclusion

In this article, I have discussed 5 stand-out features of Vue Router with examples of using them in the Vue 3 project. Apart from those 5 features, there are many exciting features of Vue Router like lazy loading, transitions, scrolling behavior, etc.

You can find more about these features in their documentation, and I invite you to try them out to see how easy it is.

Why not level up your reading with

Stay up-to-date with the latest developer news every time you open a new tab.

Read more