Introduction to Vue Router

John Au-Yeung - Feb 12 '20 - - Dev Community

To create a single-page app with Vue.js, we have to add a router library to route URLs to our components.

Vue.js has a Vue Router routing library made specifically to handle this routing.

In this article, we'll take a look at how to use the Vue Router in our Vue.js app.

Getting Started

We can get started by including Vue and Vue Router scripts in our app's page. Then, we have to add a div to house our Vue app. Also, we have to include the router-view component to view the route's content.

To add links, we add router-link components with the path that we want to go to.

To do all that, we write the following:

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <div id="app">
      <router-link to="/foo">Foo</router-link>
      <router-link to="/bar">Bar</router-link>
      <div>
        <router-view></router-view>
      </div>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

In the code above, we have the script tags for Vue and Vue Router in the head tag. Then, we have the div with ID app to house the Vue app.

The router-view is showing our content.

Inside the div, we have the router-link components with the to prop to pass in the path that we want the links to go to.

router-link is a component that comes with Vue Router.

Finally, we have our index.js script file. In there, we'll add our code for the route components and do the routing.

In index.js, we have:

const Foo = { template: "<div>foo</div>" };
const Bar = { template: "<div>bar</div>" };
const routes = [
  { path: "/foo", component: Foo },
  { path: "/bar", component: Bar }
];

const router = new VueRouter({
  routes
});

const app = new Vue({
  router,
  el: "#app"
});

In the code above, we have the Foo and Bar routes to show 'foo' and 'bar' respectively.

Then, we use the routes array to map the components to the paths which we want to map the routes to.

Next, we created a new instance of VueRouter with an object that has our routes in it.

Then, we created a new Vue instance with the router object and an el property with the #app div that we added to house our app.

Once we did all that, we should have links on the top of the page that show 'Foo' and 'Bar' links respectively. When we click them, we'll see 'foo' and 'bar' respectively.

Dynamic Route Matching

To map routes to a dynamic URL parameter, we can add a placeholder for it by creating a name and adding a colon before it.

Then, in our Vue instance, we can watch the $route object for changes in the parameter and run code accordingly.

For instance, we can write the following code to use route parameters in our routes:

index.js:

const User = {
  template: "<div>User {{id}}</div>",
  data() {
    return {
      id: undefined
    };
  },
  watch: {
    $route(to, from) {
      this.id = to.params.id;
    }
  }
};

const routes = [
    { path: "/user/:id", component: User }
];

const router = new VueRouter({
  routes
});

const app = new Vue({
  router,
  el: "#app"
});

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <div id="app">
      <router-link to="/user/1">User 1</router-link>
      <router-link to="/user/2">User 2</router-link>
      <router-view></router-view>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

In the code above, we have:

watch: {
    $route(to, from) {
      this.id = to.params.id;
    }
 }

to watch for URL parameter changes. We get the :id route parameter by using to.params.id.

Then, we set that to this.id so that we can use it in our template, which is:

<div>User {{id}}</div>

To define the routes, we have:

const routes = [
    { path: "/user/:id", component: User }
];

The :id part of the string is the URL parameter placeholder.

In index.html, we have 2 router-link components:

<router-link to="/user/1">User 1</router-link>
<router-link to="/user/2">User 2</router-link>

When we click them, we should see id in our template update as the :id route parameter is changing.

Catch-all / 404 Not Found Route

We can use the * sign as a wildcard character.

To use it, we can write:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <div id="app">
      <router-view></router-view>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

index.js

const NotFound = {
  template: "<div>not found</div>"
};

const routes = [{ path: "*", component: NotFound }];

const router = new VueRouter({
  routes
});

const app = new Vue({
  router,
  el: "#app"
});

In the code above, we have the NotFound component, which is the component for our catch-all route since we have:

{ path: "*", component: NotFound }

in the routes array.

Therefore, when we go to any URL, we'll see 'not found' displayed.

Nested Routes

We can nest routes by adding a children property to our route entries with our child routes.

For example, we can write the following to create our nested routes:

index.js:

const User = {
  template: `<div>
    User {{id}}
    <router-view></router-view>
  </div>`,
  data() {
    return {
      id: undefined
    };
  },
  watch: {
    $route(to, from) {
      this.id = to.params.id;
    }
  }
};

const Profile = {
  template: `<div>Profile</div>`
};

const routes = [
  {
    path: "/user/:id",
    component: User,
    children: [
      {
        path: "profile",
        component: Profile
      }
    ]
  }
];

const router = new VueRouter({
  routes
});

const app = new Vue({
  router,
  el: "#app"
});

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <div id="app">
      <router-link to="/user/1">User</router-link>
      <router-link to="/user/1/profile">Profile</router-link>
      <router-view></router-view>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

The difference between the nested route example above and the earlier examples is that we have the following routes definition:

const routes = [
  {
    path: "/user/:id",
    component: User,
    children: [
      {
        path: "profile",
        component: Profile
      }
    ]
  }
];

The children property is used to nest our child routes.

In the template for the User component, we have the router-view added as follows to display items in child routes:

<div>
   User {{id}}
   <router-view></router-view>
</div>

We also have the following router-link components:

<router-link to="/user/1">User</router-link>
<router-link to="/user/1/profile">Profile</router-link>

Multiple Router Views

In order to have multiple router-view components in the same app, we have to name them.

We can define our routes as follows and put them in their own router-view:

index.js:

const Foo = {
  template: `<div>foo</div>`
};

const Bar = {
  template: `<div>bar</div>`
};

const Baz = {
  template: `<div>baz</div>`
};

const router = new VueRouter({
  routes: [
    {
      path: "/",
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
});

const app = new Vue({
  router,
  el: "#app"
});

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <div id="app">
      <router-view></router-view>
      <router-view name="a"></router-view>
      <router-view name="b"></router-view>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

Protect your Vue App with Jscrambler

In the code above, we have the router-view named a and router-view named b defined as in index.html:

<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>

Then, in our routes definition in index.js, we have:

components: {
    default: Foo,
    a: Bar,
    b: Baz
}

to map the router-view with no name to the Foo component, the a router-view to the Bar component and the b router-view to the Baz component.

Then we should see:

foo
bar
baz

displayed on the screen.

Navigation Guards

From the official Vue Router docs:

Navigation guards provided by vue-router are primarily used to guard navigations either by redirecting it or canceling it.

As such, we can add navigation guards to our routes to watch for route changes and do something before it's complete.

Enter/leave navigation guards won't trigger during params or query changes.

Global Navigation Guards

We can define a global before guard by attaching a route change listener to our router.

To add a global navigation guard, we can write the following:

index.js:

const Foo = {
  template: `<div>foo</div>`
};

const Bar = {
  template: `<div>bar</div>`
};

const Login = {
  template: `<div>login</div>`
};

const router = new VueRouter({
  routes: [
    {
      path: "/foo",
      component: Foo
    },
    {
      path: "/bar",
      component: Bar
    },
    {
      path: "/login",
      component: Login
    }
  ]
});

router.beforeEach((to, from, next) => {
  if (!localStorage.getItem("authToken") && to.path !== "/login") {
    return next("/login");
  }
  next();
});

const app = new Vue({
  router,
  el: "#app"
});

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <div id="app">
      <router-link to="/foo">Foo</router-link>
      <router-link to="/bar">Bar</router-link>
      <router-view></router-view>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

In the code above, we have:

router.beforeEach((to, from, next) => {
  if (!localStorage.getItem("authToken") && to.path !== "/login") {
    return next("/login");
  }
  next();
});

which is our global navigation guard that runs before navigation begins as indicated by the beforeEach call.

We check that the path that we're going to isn't /login with:

to.path !== "/login"

Then, if localStorage.getItem("authToken") is false, we go to the /login route by calling next('./login). Otherwise, we proceed by calling next().

Per-Route Guard

In a similar way, we can define per-route guards as follows:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

These are called before navigation is done and only run when we try to go to the /foo route.

In-Component Guards

We can define in-components in our component as follows:

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    //...
  },
  beforeRouteUpdate (to, from, next) {
    //...
  },
  beforeRouteLeave (to, from, next) {
    //...
  }
}

We have 3 guards. They are:

*beforeRouteEnter - it's called before the route renders the component
*beforeRouteUpdate - it's called when a route that renders the component has changed, including parameter changes.
*beforeRouteLeave - it's called when the rendered route is about to be navigated away from.

Transitions

We can add transition components like any other component to add transitions to router-view.

To add transitions to a Vue app, we can use the transition component and some simple CSS:

styles.css:

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.9s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}

index.js:

const Foo = {
  template: `<div>foo</div>`
};

const Bar = {
  template: `<div>bar</div>`
};

const router = new VueRouter({
  routes: [
    {
      path: "/foo",
      component: Foo
    },
    {
      path: "/bar",
      component: Bar
    }
  ]
});

const app = new Vue({
  router,
  el: "#app"
});

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="app">
      <router-link to="/foo">Foo</router-link>
      <router-link to="/bar">Bar</router-link>
      <transition name="fade">
        <router-view></router-view>
      </transition>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

In the code above, we added the styles in styles.css to create our CSS route transition effects. We just changed the opacity for a short moment to add the fade effect.

Then, we added the transition component with the name attribute set to fade so that we can use the classes with the fade- prefix in styles.css to style the transition effects.

In the end, when we click on the router-link, we'll see the fade effect.

Conclusion

We can use Vue Router to map URL paths to components.

To get route params, we watch the $route object in our components.

We can also add nested routes by adding a children property with nested routes.

Also, we can add a components property to our route and name our router-view to add multiple router-view.

To intercept navigation and do something, we can add navigation guards for various stages of navigation.

Finally, we can use the transition component with some CSS to create route transition effects.


Before deploying your commercial or enterprise Vue apps to production, make sure you are protecting their code against reverse-engineering, abuse, and tampering by following this tutorial.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .