Categories
JavaScript Mega-Guides Vue

Vue Router Mega-Guide

Spread the love

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In order to create a single-page app with Vue.js, we have to map URLs to components so that when users go to a URL, it’ll show the corresponding component.

In this article, we’ll look at how to create some simple routes with the Vue Router.

Getting Started

We can use by Vue Router by adding a script tag with the URL for the Vue Router library.

We can make a simple app as follows:

src/index.js :

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  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/foo">Foo</router-link>  
        <router-link to="/bar">Bar</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we defined 2 components Foo and Bar in src/index.js .

Then we mapped them to routes with:

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

const router = new VueRouter({  
  routes  
});

Then we created a new Vue instance with:

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

In the template, we have router-links to map the routes to a tags with the URLs to correspond to the routes we define:

<router-link to="/foo">Foo</router-link>  
<router-link to="/bar">Bar</router-link>

Then we have router-view to show the components that are mapped to routes:

<router-view></router-view>

In the end, we should get:

Foo Link Bar Linkfoo

when we click on Foo Link .

and:

Foo Link Bar Linkbar

when we click on Bar Link .

Once we injected the router, we also get access to this.$router .

We can use it to navigate between routes. For example, we can write the following:

src/index.js :

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  
});

new Vue({  
  el: "#app",  
  router,  
  methods: {  
    goBack() {  
      window.history.length > 1 ? this.$router.go(-1) : this.$router.push("/");  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/foo">Foo Link</router-link>  
        <router-link to="/bar">Bar Link</router-link>  
        <a href="#" @click="goBack">Go Back</a>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then when we click the Foo Link and Bar Link links a few times then click Go Back , we’ll see it’ll go back to the previous routes that we navigated to.

Route Parameters

We can get route parameters with the this.$route.params object.

For example, we can write an app that shows the URL parameter that’s passed in as follows:

src/index.js :

const User = {  
  computed: {  
    username() {  
      return this.$route.params.username;  
    }  
  },  
  template: `<div>{{username}}</div>`  
};

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/user/foo">Foo</router-link>  
        <router-link to="/user/bar">Bar</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The :username in “/user/:username” is the parameter, so we can get what passed in after /user/ by using this.$route.params.username .

We added a computed property username which returns this.$route.params.username so we can use:

`<div>{{username}}</div>`

to show the username URL parameter.

Route Parameters

We can get route parameters with the this.$route.params object.

For example, we can write an app that shows the URL parameter that’s passed in as follows:

src/index.js :

const User = {  
  computed: {  
    username() {  
      return this.$route.params.username;  
    }  
  },  
  template: `<div>{{username}}</div>`  
};

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/user/foo">Foo</router-link>  
        <router-link to="/user/bar">Bar</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The :username in “/user/:username” is the parameter, so we can get what passed in after /user/ by using this.$route.params.username .

We added a computed property username which returns this.$route.params.username so we can use:

`<div>{{username}}</div>`

to show the username URL parameter.

We can also have multiple parameters in one route.

For example, we can write the following:

src/index.js :

const Name = {  
  template: `<div>  
    {{$route.params.firstName}}   
    {{$route.params.lastName}}  
  </div>`  
};

const routes = [{ path: "/name/:firstName/:lastName", component: Name }];

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/name/Jane/Doe">Jane</router-link>  
        <router-link to="/name/Mary/Smith">Mary</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then when we click on the links, we get the first name and last name that we passed into the URL parameters.

:firstName and :lastName are matched to the params by position.

Reacting to Params Changes

We can watch the $route object to watch for param changes. Routes that the same name but different params use the same components. They won’t be re-rendered from scratch, so the lifecycle hooks won’t be called.

For example, we can write the following:

src/index.js :

const Name = {  
  data() {  
    return {  
      oldName: "",  
      newName: ""  
    };  
  },  
  watch: {  
    $route(to, from) {  
      this.oldName = from.params.name;  
      this.newName = to.params.name;  
    }  
  },  
  template: `<div>  
    Old: {{oldName}}   
    New: {{newName}}  
  </div>`  
};

const routes = [{ path: "/name/:name", component: Name }];

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/name/Jane">Jane</router-link>  
        <router-link to="/name/Mary">Mary</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we have the Name component that’s mapped to a route that takes the name route parameter.

Also, Name has watcher for the $route object that takes the to and from parameters. They are the previous and current routes respectively.

So when we click back and forth between links, we’ll see the old parameter and new parameter displayed.

Catch all / 404 Not found Route

We can add * as a wildcard character for routes.

For example, we can use:

path: '*'

to match all routes and:

path: '/user-*'

to match anything that begins with user- .

For example, we can use the wildcard character as follows:

src/index.js :

const Foo = { template: "<p>foo</p>" };  
const Bar = { template: "<p>bar</p>" };  
const NotFound = { template: "<p>not found</p>" };  
const routes = [  
  { path: "/foo", component: Foo },  
  { path: "/bar", component: Bar },  
  { path: "*", component: NotFound }  
];

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/foo">Foo</router-link>  
        <router-link to="/bar">Bar</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

When we go to any URL other than /foo or /bar , we’ll see the ‘not found’ message.

We can use the wildcard character with other text as follows:

src/index.js :

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/foo">Foo</router-link>  
        <router-link to="/bar">Bar</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we see foo when we go to any URL that starts with foo .

Regular Expressions

We can use regular expressions to match route parameters. For example, we can write:

src/index.js :

const Foo = { template: "<p>foo {{$route.params.id}}</p>" };  
const Bar = { template: "<p>bar</p>" };  
const routes = [  
  { path: "/foo/:id(\d+)", component: Foo },  
  { path: "/bar", component: Bar }  
];

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/foo">Foo</router-link>  
        <router-link to="/bar">Bar</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then when we go to /foo/1 we see foo 1 . If what comes after /foo/ isn’t a number, then we won’t see anything.

Redirect

We can add redirects from one route to another by adding the redirect property to a route.

For example, we can add a redirect as follows:

src/index.js :

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

Then when we go to /#/bar in the browser, we get bar displayed.

A redirect can also target a named route. For example, we can write:

src/index.js :

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

Then we get the same result.

We can also set the redirect property to a function. For instance, we can set it to a function that returns a route as follows:

src/index.js :

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

const router = new VueRouter({  
  routes  
});

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

Then we get the same result as the previous examples.

Navigation guards aren’t applied on routes that redirect, only on its target. Alias

A redirect means that when a user visits /foo then the URL will be replaced by /bar and then matched as /bar .

An alias means of /foo that’s set as /bar means that when the user visits /bar , the URL remains /bar , but it’ll be matched as if the user is visiting /foo .

For example, we can define a route with an alias as follows:

src/index.js :

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

Then we get foo when we go to /#/foo or /#/bar .

An alias lets us map any route to any URL without being constrained by the nesting structure of the routes.

This is handy for mapping a URL that we want for a nested route.

For example, if we define an alias for a nested route as follows:

src/index.js :

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

Then when we go to /#/foobar , we get:

foobar

displayed on the screen.

We also get the same thing displayed if we go to /#/foo/bar .

Therefore, we don’t have to follow the usual convention for nested route URLs if we use an alias.

Adding Meta Fields to Routes

We can add meta fields to routes so that they can be checked in navigation guards and components.

For example, we can add a meta field indicating which fields need authentication and then check if an auth token is stored in local storage as follows:

src/index.js :

const Login = {  
  template: "<div>login</div>"  
};  
const Profile = {  
  template: "<div>profile</div>"  
};  
const routes = [  
  {  
    path: "/",  
    component: Login  
  },  
  {  
    path: "/profile",  
    component: Profile,  
    meta: { requireAuth: true }  
  }  
];

const router = new VueRouter({  
  routes  
});

router.beforeEach((to, from, next) => {  
  if (to.meta.requireAuth) {  
    if (!localStorage.getItem("token")) {  
      next("/");  
    } else {  
      next();  
    }  
  } else {  
    next();  
  }  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

The code above has a global beforeEach guard to check if meta field with requiredAuth set to true .

Then if it is, we’ll check if localStorage.token exists, and then complete navigation if it’s present.

Otherwise, we cancel navigation of the current route and navigate to / .

In all other cases, we continue with the navigation.

Accessing Route Meta Field within Components

We can access the meta field in components using the $route.meta field.

For example, we can write the following to get the id meta field displayed in our template:

src/index.js :

const User = {  
  template: "<div>user {{$route.meta.id}}</div>"  
};

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

Then we should see user 1 displayed since we have $route.meta.id in our template.

Adding Transition Effect

We can apply transition effects to router-view the same way as any other component.

For example, we can apply per-route transition by writing the following:

src/index.js :

const Foo = {  
  template: `  
    <transition name="slide">  
      <div>foo</div>  
    </transition>  
  `  
};

const Bar = {  
  template: `  
    <transition name="fade">  
      <div>bar</div>  
    </transition>  
  `  
};

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

const router = new VueRouter({  
  routes  
});

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

src/styles.css :

.fade-enter-active,  
.slide-enter-active {  
  transition: all 0.3s ease;  
}  
.fade-leave-active,  
.slide-leave-active {  
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);  
}  
.fade-enter,  
.fade-leave-to,  
.slide-enter,  
.slide-leave-to {  
  transform: translateX(10px);  
  opacity: 0;  
}

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>  
    <link href="src/styles.css" type="text/css" />  
  </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="src/index.js"></script>  
  </body>  
</html>

Then we see that there’s fade effect as we transition from one route to another by clicking the links.

We can then use different names for each route.

Route-Based Dynamic Transition

We can also do route based dynamic transitions by combining a watcher for $route and dynamically setting the transition name as follows:

src/index.js :

const Foo = {  
  template: ` 
    <transition name="slide">  
      <div>foo</div>  
    </transition>  
  `  
};

const Bar = {  
  template: `  
    <transition name="fade">  
      <div>bar</div>  
    </transition>  
  `  
};

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

const router = new VueRouter({  
  routes  
});

new Vue({  
  el: "#app",  
  router,  
  data: {  
    transitionName: ""  
  },  
  watch: {  
    $route(to, from) {  
      this.transitionName = to.path === "/bar" ? "slide-right" : "slide-left";  
    }  
  }  
});

src/styles.css :

.slide-left-enter-active,  
.slide-right-enter-active {  
  transition: all 0.3s ease;  
}  
.slide-left-leave-active,  
.slide-right-leave-active {  
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);  
}  
.slide-right-enter,  
.slide-right-leave-to {  
  transform: translateX(10px);  
  opacity: 0;  
}

.slide-left-enter,  
.slide-left-leave-to {  
  transform: translateX(-10px);  
  opacity: 0;  
}

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>  
    <link href="src/styles.css" type="text/css" />  
  </head>  
  <body>  
    <div id="app">  
      <router-link to="foo">Foo</router-link>  
      <router-link to="bar">Bar</router-link>  
      <transition :name="transitionName">  
        <router-view></router-view>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then when we click on Foo , bar fades away by sliding to the left. And when we click Bar , foo fades away by sliding to the right.

Global Before Guards

Router parameter or query changes won’t trigger enter or leave navigation guards.

We can watch the $route object or use the beforeRouteUpdate in-component guard to check those changes.

For other route changes, we can use navigation guards to run checks before navigating to a route.

The beforeEach method takes a callback as follows:

const router = new VueRouter({ ... })  
  
router.beforeEach((to, from, next) => {  
  // ...  
})

The 3 parameters are the following:

  • to — the target route object being navigated to
  • from — the current route that’s being navigated from
  • next — a function that’s called to resolve the hook

The next function takes one argument. It can take on several possible values.

If nothing is passed in, then we can navigate to the next route. If we pass in false , then the current navigation is aborted.

If we pass in '/' or { path: '/' } , then we redirect to the / route. The current navigation is aborted and a new one is started. We can also set the replace and name properties like as we do with router.push or router.replace .

We can also pass in an Error instance since Vue 2.4.0, then the navigation will be aborted and the error will be passed to callbacks registered via router.onError() .

We should only call next once in our guard.

For example, we can create a navigation guard to check navigation as follows:

src/index.js :

const Login = { template: "<div>login</div>" };  
const Profile = { template: "<div>profile</div>" };  
const routes = [  
  {  
    path: "/",  
    component: Login  
  },  
  {  
    path: "/profile",  
    component: Profile  
  }  
];

const router = new VueRouter({  
  routes  
});

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

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

In the code above, our navigation guard first checks if we’re going to / , if we are, then we let them navigate to it.

Otherwise, we check if localStorage.token is defined. If it is, then we let them through. Otherwise, we go back to / .

Therefore, if localStorage.token is defined, then we can go to /#/profile and see profile displayed. Otherwise, we get redirected back to /#/ and see login.

Global Resolve Guards

Vue Router also has router.beforeResolve , which is similar to router.beforeEach but it’s called right before navigation is confirmed, and after all in-component guards and async route components are resolved.

Global After Hooks

We can also run code after navigation is done with the afterEach hook.

For example, we can use it as follows:

src/index.js :

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

const router = new VueRouter({  
  routes  
});

router.afterEach((to, from) => {  
  alert(`Navigated to ${to.path}`);  
});  
new Vue({  
  el: "#app",  
  router  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

Then we get an alert box that shows where we navigate to when we navigate.

Note that it doesn’t have the next function in the callback signature because it can’t go to the next route.

Per-Route Guard

We can also define navigation guards per route. For example, we can define it as follows:

src/index.js :

const Login = { template: "<div>login</div>" };  
const Profile = { template: "<div>profile</div>" };  
const routes = [  
  {  
    path: "/",  
    component: Login  
  },  
  {  
    path: "/profile",  
    component: Profile,  
    beforeEnter: (to, from, next) => {  
      if (!localStorage.getItem("token")) {  
        next("/");  
      } else {  
        next();  
      }  
    }  
  }  
];

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

The beforeEnter hook for /profile checks if localStorage.token is defined. If it is, then we let them through. Otherwise, we go back to / .

Therefore, if localStorage.token is defined, then we can go to /#/profile and see profile displayed. Otherwise, we get redirected back to /#/ and see login.

The signature is the same as the global beforeEach callback.

Programmatic Navigation

We can navigate through routes programmatically in addition to using router-link to create a link to let users navigate through routes.

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

To do this, we can use the $router instance available to a component to navigating routes programmatically.

router.push(…) is called when a router-link link is clicked. We can call it ourselves to programmatically navigate to routes.

For example, we can define routes and navigate through them programmatically as follows:

src/index.js :

const Foo = { template: "<p>foo</p>" };  
const Bar = { template: "<p>bar</p>" };

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

const router = new VueRouter({  
  routes  
});

new Vue({  
  el: "#app",  
  router,  
  methods: {  
    goTo(route) {  
      this.$router.push(route);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <a href="#" @click='goTo("foo")'>Foo</a>  
        <a href="#" @click='goTo("bar")'>Bar</a>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we have the goTo method that takes a string for the route that we want to go to.

In the method, we call this.$router.push(route); to go to the route we want to reach.

So when we click on Foo we see foo , and when we click on Bar we see bar .

We can also pass in an object as follows:

this.$router.push({ path: route });

Also, we can go to named routes when $router.push . To do this, we write:

src/index.js :

const Foo = { template: "<p>foo</p>" };  
const Bar = { template: "<p>bar</p>" };

const routes = [  
  {  
    name: "foo",  
    path: "/foo",  
    component: Foo  
  },  
  {  
    name: "bar",  
    path: "/bar",  
    component: Bar  
  }  
];

const router = new VueRouter({  
  routes  
});

new Vue({  
  el: "#app",  
  router,  
  methods: {  
    goTo(name) {  
      this.$router.push({ name });  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <a href="#" @click='goTo("foo")'>Foo</a>  
        <a href="#" @click='goTo("bar")'>Bar</a>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we defined named routes by adding the name property to our routes by writing:

const routes = [  
  {  
    name: "foo",  
    path: "/foo",  
    component: Foo  
  },  
  {  
    name: "bar",  
    path: "/bar",  
    component: Bar  
  }  
];

Then we can go to a route by name as follows in the goTo method:

this.$router.push({ name });

We can pass in route parameters as follows:

router.push({ name: 'user', params: { userId: '123' } })

The following won’t work:

router.push({ path: 'user', params: { userId: '123' } })

We can go to routes with query strings as follows:

router.push({ path: 'user', query: { userId: '123' } })

or:

router.push({ name: 'user', query: { userId: '123' } })

We can also go on a path with a route parameter as follows:

router.push({ path: `/user/123` });

For example, we can use them as follows:

const Foo = { template: "<p>foo {{$route.query.id}}</p>" };  
const Bar = { template: "<p>bar</p>" };

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

const router = new VueRouter({  
  routes  
});

new Vue({  
  el: "#app",  
  router,  
  methods: {  
    goTo(path, query) {  
      this.$router.push({ path, query });  
    }  
  }  
});

Then we see foo 1 when we click on Foo since we take a query string with id as the key.

It’s the same as going to /#/foo?id=1 in the browser.

The same rules apply for the to property of the router-link component.

In Vue Router 2.2.0 or later, we can optionally provide a onComplete and onAbort callbacks to router.push or router.replace as the 2nd and 3rd arguments.

In Vue Router 3.1.0+. router.push and router.replace will return promises and we don’t need to pass in the 2nd and 3rd arguments to handle those cases.

If our destination is the same as the current route and only the parameters are changing, like /users/1 to /users/2 , then we have to use beforeRouteUpdate hook to react to changes.

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

router.replace acts like router.push except that no new history entry is added.

router.replace(…) is the same as <router-link :to=”…” replace> .

router.go(n)

We can use router.go to go forward or backward by passing in an integer for the number of steps to go forward or back. Negative is backward and positive is forward.

For example, we can use it as follows:

src/index.js :

const Foo = { template: "<p>foo</p>" };  
const Bar = { template: "<p>bar</p>" };

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

const router = new VueRouter({  
  routes  
});

new Vue({  
  el: "#app",  
  router,  
  methods: {  
    forward() {  
      this.$router.go(-1);  
    },  
    back() {  
      this.$router.go(1);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="foo">Foo</router-link>  
        <router-link to="bar">Bar</router-link>  
        <a href="#" @click="forward">Forward</a>  
        <a href="#" @click="back">Back</a>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

We have to forward and back methods to go forward and backward respectively.

router.go will fail silently if no such history record exists.

Nested Routes

We need nested routes to organize routes where components are nested multiple levels deep.

For example, we have to find a way to organize routes like:

/user/bar/profile                       
/user/bar/posts

into one coherent place.

Defining Nested Routes

We can define nested routes by adding a children array in the route entry, and then in the parent route’s template we have to add router-view to display the children routes.

For example, we can write the following:

src/index.js :

const Profile = { template: "<p>{{$route.params.user}}'s profile</p>" };  
const Posts = { template: "<p>{{$route.params.user}}'s posts</p>" };  
const User = {  
  template: `  
    <div>  
      <p>user - {{$route.params.user}}</p>  
      <router-view></router-view>  
    </div>  
`  
};  
const routes = [  
  {  
    path: "/user/:user",  
    component: User,  
    children: [  
      { path: "posts", component: Posts },  
      { path: "profile", component: Profile }  
    ]  
  }  
];

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/user/foo">Foo User</router-link>  
        <router-link to="/user/foo/posts">Foo Post</router-link>  
        <router-link to="/user/foo/profile">Foo Profile</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we have:

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

to define the child routes.

The Posts and Profile components are like any component we defined as usual.

However, the User component has the router-view to display them content of the Posts and Profile components.

In the template, we have 3 router links to go to the root and child routes:

<router-link to="/user/foo">Foo User</router-link>  
<router-link to="/user/foo/posts">Foo Post</router-link>  
<router-link to="/user/foo/profile">Foo Profile</router-link>

Then we should see:

Foo User Foo Post Foo Profile  
user - foofoo's posts

when we go to /#/user/foo/posts or click on Foo Post .

And we should see:

Foo User Foo Post Foo Profileuser - foofoo's profile

when we go to /#/user/foo/profile or click on Foo Profile.

When we go to /#/user/foo or click on Foo User , we see:

Foo User Foo Post Foo Profileuser - foo

Any nested path that starts with / will be treated as the root path. This lets us use component nesting without having to use a nested URL.

Also, the parent and child routes all have access to the same route parameters.

For example, we can add a UserHome component and a route for it to the example above as follows:

src/index.js :

const Profile = { template: "<p>{{$route.params.user}}'s profile</p>" };  
const Posts = { template: "<p>{{$route.params.user}}'s posts</p>" };  
const UserHome = { template: "<p>{{$route.params.user}} home</p>" };  
const User = {  
  template: `  
    <div>  
      <p>user - {{$route.params.user}}</p>  
      <router-view></router-view>  
    </div>`  
};  
const routes = [  
  {  
    path: "/user/:user",  
    component: User,  
    children: [  
      { path: "", component: UserHome },  
      { path: "posts", component: Posts },  
      { path: "profile", component: Profile }  
    ]  
  }  
];

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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">  
      <div>  
        <router-link to="/user/foo">Foo User</router-link>  
        <router-link to="/user/foo/posts">Foo Post</router-link>  
        <router-link to="/user/foo/profile">Foo Profile</router-link>  
      </div>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Now when we go to /user/foo or click on Foo User , we see:

Foo User Foo Post Foo Profileuser - foofoo home

Everything else remains the same.

Per-Route Guard

We can also define navigation guards per route. For example, we can define it as follows:

src/index.js :

const Login = { template: "<div>login</div>" };  
const Profile = { template: "<div>profile</div>" };  
const routes = [  
  {  
    path: "/",  
    component: Login  
  },  
  {  
    path: "/profile",  
    component: Profile,  
    beforeEnter: (to, from, next) => {  
      if (!localStorage.getItem("token")) {  
        next("/");  
      } else {  
        next();  
      }  
    }  
  }  
];

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

The beforeEnter hook for /profile checks if localStorage.token is defined. If it is, then we let them through. Otherwise, we go back to / .

Therefore, if localStorage.token is defined, then we can go to /#/profile and see profile displayed. Otherwise, we get redirected back to /#/ and see login.

The signature is the same as the global beforeEach callback.

In-Component Guards

We can define navigation guards inside a component, the hooks are the following:

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave

For example, we can use them as follows:

beforeRouteEnter

If we want to check if a token is present before proceeding to a route, we can write some like the following code:

src/index.js :

const Login = { template: "<div>login</div>" };  
const Profile = {  
  data() {  
    return { user: "foo" };  
  },  
  template: "<div>profile</div>",  
  beforeRouteEnter(to, from, next) {  
    if (!localStorage.getItem("token")) {  
      return next("/");  
    }  
    next(vm => {  
      alert(\`You're logged in as ${vm.user}`);  
    });  
  }  
};  
const routes = [  
  {  
    path: "/",  
    component: Login  
  },  
  {  
    path: "/profile",  
    component: Profile  
  }  
];

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

In the code above, we have the beforeRouteEnter hook to check if the token is present in local storage. If it is then, we proceed. Otherwise, we go back to / .

If we proceed, then next is called with a callback with vm , which is the component object as the parameter. Therefore, we can get the user property from vm in the next callback.

In the end, we should see ‘You’re logged in as foo’ alert box displayed if a localStorage.token is defined.

Only beforeRouteEnter accepts a callback for next since the component isn’t available before navigation is done.

beforeRouteUpdate

We can use beforeRouteUpdate as follows:

src/index.js :

const Profile = {  
  data() {  
    return {  
      id: undefined  
    };  
  },  
  template: "<div>id: {{id}}</div>",  
  beforeRouteUpdate(to, from, next) {  
    this.id = to.params.id;  
    next();  
  }  
};  
const routes = [  
  {  
    path: "/profile/:id",  
    component: Profile  
  }  
];

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

Then when we go /#/profile/1 , then /#/profile/2 , we’ll see id: 2 displayed.

beforeRouteUpdate watches for route updates, so it does nothing when the route first loads.

beforeRouteLeave

We can use beforeRouteLeave to run code before a user navigates away from a route.

For example, we can use it as follows:

src/index.js :

const Profile = {  
  template: "<div>profile</div>",  
  beforeRouteLeave(to, from, next) {  
    const answer = window.confirm("Are you sure you want to leave?");  
    if (answer) {  
      next();  
    } else {  
      next(false);  
    }  
  }  
};  
const routes = [  
  {  
    path: "/profile",  
    component: Profile  
  }  
];

const router = new VueRouter({  
  routes  
});

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

index.html:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

In the code above, we have the beforeRouteLeave hook that runs before a user tries to leave a route.

We have:

const answer = window.confirm("Are you sure you want to leave?");

to ask the user if he wants to leave. If he clicks OK, then the navigation will continue by calling next() . Otherwise, next(false) is called and navigation is canceled.

Full Navigation Resolution Flow

The full flow for navigation resolution is as follows:

  1. navigation triggered
  2. beforeRouteLeave called
  3. global beforeEach guard called
  4. beforeRouterUpdate in component called
  5. beforeEnter in route configs called
  6. resolve async route components
  7. beforeEnter in activated components called
  8. global beforeResolve guards called
  9. navigation confirmed
  10. global afterEach hooks called
  11. DOM updates triggered
  12. callbacks passed to next in beforeRouteEnter called

Data Fetching

We sometimes have to fetch data from the server when a route is activated. For example, we may want to fetch user data when a route is loaded.

With Vue Router, we can fetch data after navigation or before.

When we fetch data after navigation, we perform the navigation first, then fetch data in the component’s lifecycle hook and display a loading state while data is being fetched.

When we fetch data before navigation, we fetch the data as the route enters a navigation guard, then perform the navigation after data is fetched.

Fetching After Navigation

We can fetch data after navigation by navigation and render the component first, the fetch the data in the component’s created hook.

This gives us the opportunity to display a loading state while the data is being fetched and we can also handle loading differently in each view.

For example, we load data after navigation as follows:

src/index.js :

const User = {  
  data() {  
    return {  
      user: {}  
    };  
  },  
  created() {  
    this.fetchData();  
  },  
  watch: {  
    $route: "fetchData"  
  },  
  methods: {  
    async fetchData() {  
      this.user = await Promise.resolve({ id: this.$route.params.id });  
    }  
  },  
  template: `      
    <div>user: {{user.id}}</div>      
  `  
};

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

In the code above, we have the User component that has the fetchData to fetch data by resolving a promise and setting the result to this.user . This is done each time the $route object changes.

So when we go to /#/1 , we get user: 1 , and when we go to /#/2 , we get user: 2 .

We can add a loading state to fetchData as follows:

const User = {  
  data() {  
    return {  
      user: {},  
      loading: true  
    };  
  },  
  created() {  
    this.fetchData();  
  },  
  watch: {  
    $route: "fetchData"  
  },  
  methods: {  
    async fetchData() {  
      this.loading = true;  
      this.user = await Promise.resolve({ id: this.$route.params.id });  
      this.loading = false;  
    }  
  },  
  template: `    
    <div>    
      <div v-if='!loading'>user: {{user.id}}</div>      
      <div v-if='loading'>Loading</div>  
    </div>  
  `  
};

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

const router = new VueRouter({  
  routes  
});

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

Then when the data loads in fetchData , we have this.loading set to true . Otherwise, it’s set to false .

Fetching Before Navigation

we can fetch data before navigating to a new route by using the beforeRouteEnter hook.

Then in the next hook, we can pass in a callback that calls a method in our component to set the data.

For example, we can write the following code to do that:

src/index.js :

const User = {  
  data() {  
    return {  
      user: {},  
      loading: true  
    };  
  },  
  async beforeRouteEnter(to, from, next) {  
    const user = await Promise.resolve({ id: to.params.id });  
    next(vm => vm.setUser(user));  
  },  
  async beforeRouteUpdate(to, from, next) {  
    const user = await Promise.resolve({ id: to.params.id });  
    this.setUser(user);  
    next();  
  },  
  methods: {  
    setUser(user) {  
      this.loading = true;  
      this.user = user;  
      this.loading = false;  
    }  
  },  
  template: `    
    <div>    
      <div v-if='!loading'>user: {{user.id}}</div>      
      <div v-if='loading'>Loading</div>  
    </div>  
  `  
};

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

const router = new VueRouter({  
  routes  
});

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

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <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="src/index.js"></script>  
  </body>  
</html>

In the code above, the beforeRouteEnter hook loads the data before the component is fully loaded, so we have to call next(vm => vm.setUser(user)); to set data.user . vm is the component instance, so the setUser method is a property of vm .

When the route parameter updates, beforeRouteUpdate will be called. We can get the data and call this.setUser directly since the component is already loaded.

Then when we go to /#/1 , we get user: 1 , and when we go to /#/2 , we get user: 2 .

It’s also a good idea to display a progress bar or some kind of indicator while data is loading.

Conclusion

Vue Router is the choice for routing for a Vue app.

It supports many common features like route parameters, route guards, nested routes, and more.

Integration with Vue is solid since it’s made by the same team.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *