One of the biggest draws of VueJS is the ability to build great Single Page Applications (SPAs).

SPAs are great because they don't require page loads every time the route changes. This means that once everything is loaded, we can switch the view really quickly and provide a great user experience.

If you want to build a SPA in Vue, you're going to need Vue Router.

In this tutorial, we'll be going over the basics of setting up Vue Router as well as looking into some more advanced techniques like:

  • Dynamic Route Matching
  • and Navigation Hooks

Let's dive right in!

Table of contents:


    What is Vue Router?

    Vue Router helps link between the browser's URL/History and Vue's components allowing for certain paths to render whatever view is associated with it.

    A VueConf Toronto talk by Eduardo San Martin Morote, a member of the VueCore team, gave some more insight into the design ideology behind Vue Router.

    Morote discussed the decision process behind finding the balance between a flexible router (devs have more freedom, but write more code) and an opinionated one (devs have less freedom, but the router covers more use cases).

    Vue Router, which is configuration-based, aims to provide developers with tools for common use cases AND be flexible for unique problems.

    Now, let's cover the basics before moving on to some more advanced Vue Router topics.

    A Quick Setup of Vue Router

    Let's quickly go set up a simple example of Vue Router.

    While you can easily include Vue Router using vue-cli, I think it's worthwhile to know how to install it yourself. This way, you can truly know all the different aspects of Vue Router.

    First, we add Vue Router to our project using npm install vue-router. Then, we include it inside our Vue instance with our src/main.js file.

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'// loads from src/router/index.js
        
    new Vue({    
      router,
      render: h => h(App),
    }).$mount('#app')
    

    With all that setup out of the way, let's actually create our router.

    Inside src, create an src/router folder that contains an index.js file with the following contents.

    import Vue from 'vue'
    import VueRouter from  'vue-router'
    import Home from '../views/Home.vue'
    import Profile from '../views/Profile.vue'
    
    Vue.use(VueRouter);
    
    const routes = [
      {
        path: "/",
        name: "home",
        component: Home
      },
      {
        path: '/profile',
        name: 'profile',
        component: Profile
      }
    ]
    
    const router = new VueRouter({
      mode: 'history',
      routes
    })
    
    export default router
    

    This code snippet initializes Vue Router with two example route-component matches. I won't go into the details of the Home and Profile components, so we'll just assume they output "home" and "profile", respectively.

    Displaying with router-view

    Okay. We have our Vue Router setup, but we still don't have any way of actually viewing it.

    That's where the <router-view> element comes in. Essentially, the router-view element gives Vue Router a location to render whatever component the current URL resolves to.

    For our example, we'll place it in the App.vue root component. Let's also add some links so that we can switch between our two routes. Vue Router uses special link elements called <router-link> that have a to attribute that map to a component.

    <template>
      <div id="app">
        <router-link to='/'>Home</router-link>
        <router-link to='/profile'>Profile</router-link>
        <router-view  />
      </div>
    </template>
    

    When we run our app, we should see our home component rendering. If we click our router-link elements, the content will change and the URL will too!

    Now, let's dive into more of the specifics about Vue Router.

    Programatically Changing the Route

    In our previous example, we navigated between our different routes using <router-link> elements. These essentially are Vue Router's equivalent of anchor tags (in fact they compile to anchor tags).

    However, another way to change our route is to programmatically navigate using the router.push method. Similar to using router link elements, router.push accepts a string or object that maps to one of our routes by using its path or its name.

    this.$router.push({ path: '/profile' })
    // OR
    this.$router.push({ name: 'profile' })
    

    It's also easy to pass URL parameters or queries using this method. We just have to add a params or query argument.

    this.$router.push({ path:  '/profile', params: { username:  'helloworld' }, query: { source:  'tutorial' } })
    

    The Difference Between History Mode and Hash Mode

    Vue Router has two modes for its URL - history and hash modes.

    • Hash Mode (default) - uses URL hash to simulate a URL; e.g. mypage.com/#profile

    • History - looks like a typical URL and uses history.pushState to avoid page reloads; e.g. mypage.com/profile

    For our router, we used history mode because I personally like the standard URL look.

    Handling Dynamic Routes in Vue Router

    Vue Router also lets you match a URL pattern to a component instead of having to hardcode every possible route. This is extremely useful for configuring posts, profile pages, or other content that can be dynamically created/removed.

    We use a colon : to define a dynamic path in Vue router. For example, if we wanted to dynamically match posts, the route would look like this.

    {
      path:  '/post/:postID',
      name:  'post',
      component:  ArticlePage
    }
    

    This route navigates every URL with the /post/:postID pattern to the same ArticlePage.vue component

    If we want to be to get the postID inside our component, there are two ways to accomplish this.

    1. Our postID will be accessible in ArticlePage via the $route.params object

    2. We can pass postID as a prop to our component.

    I recommend the second method because it allows you to build more reusable components that are not dependent on a specific URL format.

    To do this, we only have to add props: true to our route. After adding that property, our dynamic route should look like this.

    {
      path:  '/post/:postID',
      props: true,
      name:  'post',
      component:  ArticlePage
    }
    

    Then, in our component, we have to make sure that we declare a prop with the same name that we declared in our router.

    <template>
      <div>
        {{ postID }}
      </div>
    </template>
    <script>
    export  default {
      props: {
        postID:  String
      }
    }
    </script>
    

    In this example, if we go to to the url http://localhost:8080/post/my-post then our page will render my-post.

    In more complete projects, we'd typically take the prop value our router passes and make an API call to load in the corresponding content. However, once you have access to the prop inside the component, you can do whatever you want with it.

    An Introduction to Navigation Guards

    Navigation guards are one of the more advanced topics in Vue Router. They are hooks during the routing process that let you redirect, cancel, or modify navigation.

    There are three types of navigation guards:

    1. Global Guards

    2. Route Specific Guards

    3. In Component Guards

    Additionally, each guard can take three arguments:

    • to - the route we want to go to

    • from - the route we are leaving

    • next - a function that is used to resolve the hook; depending on the argument we pass to the next method, our router will handle different navigations

      • next(false) - aborts the navigation and we dont leave the from route

      • next('/home') - redirects our navigation to the specified route

      • next() - no argument simply moves it to the next hook; confirms navigation there are no hooks left

    1. Global Guards

    There are two main global guards: router.beforeEach() and router.afterEach() which run (you guessed it!) before and after our navigation resolves.

    Let's look at an example. In this, we check if a user has access to a certain page, and if they do not, we stop the route from resolving. Notice how we call next once and only once every time this hook runs.

    router.beforeEach( (to, next, from) => {
      if (to.path === '/profile') {
        if (!hasAccess(to.path)) { // just some arbitrary conditional
    	    next(false) // deny access to this page
        } else {
    	    next() // keep moving on to next hook
        }
      } else {
        next() // keep moving on to next hook
      }
    })  
    

    2. Route Specific Guards

    When we declare our routes in Vue Router, we can also add a beforeEnter function that acts just like the global beforeEach route, but it can contain route-specific logic.

    {
      path:  '/post/:postID',
      props:  true,
      name:  'post',
      component:  ArticlePage,
      beforeEnter: (to, from, next) => {
        // some logic here
      }
    }
    

    3. In Component Guards

    Even more specifically, we can insert navigation guards inside a component's options object. There are three guards that we can include.

    beforeRouteEnter (to, from, next) - called before we confirm this route; the component has not been created yet.

    beforeRouteUpdate (to, from, next) - called when we are switching routes; but the new route resolves to this component too.

    beforeRouteLeave(to, from, next) - called when we are navigating away from this component

    An important thing to note is that beforeRouteEnter is called before our navigation is confirmed and before our component is actually created. So we don't have access to this yet.

    To fix this, beforeRouteEnter allows us to pass a callback to our next method that will run once our component has actually been created.

    beforeRouteEnter (to, from, next) {
      next((vm) => {
        // vm = 'this'
    	console.log(vm)
      })
    }
    

    Wrapping it Up

    Hopefully, this post helped teach you some basic and advanced Vue Router techniques.

    I think it's really interesting to see the design choices that went into making a flexible, but easy to use router. However, listening to Morote's talk makes it seem like there are even more improvements coming!

    How are you using Vue Router in your projects? I'd love to know.


    Matt Maribojoc

    About Matt Maribojoc

    Matt Maribojoc is currently working on learnvue.co - a website with tons of VueJS tutorials and tips/tricks. He's been writing about VueJS since 2018 and is currently a student at the University of Pittsburgh.