Vue Router - The Complete Guide

Cover image

If you are using Vue, the odds are you will need to deal with the Vue router. Let's go through all the common use-cases you will need! 👌

In this tutorial, we will cover the most essential router concepts as well as more advanced patterns such as guarded routes & animated routes.

Are you ready? Let's do this! 💪

Overview

Let's have a look at the bigger picture first and then dig deeper.

Project structure

I have created a small vue project to demonstrate different functionalities of the Vue Router. The project has a standard setup using the vue-cli.

├── README.md
├── babel.config.js
├── package.json
├── postcss.config.js
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   ├── router.js
│   └── views
│       ├── Animated.vue
│       ├── Dynamic.vue
│       ├── Guarded.vue
│       ├── Home.vue
│       ├── LazyLoaded.vue
│       ├── Login.vue
│       ├── Nested.vue
│       └── WithProps.vue
└── yarn.lock

We will be dealing mostly with the router.js but also different views.

Here is how the main router configurations look like:

import Vue from 'vue';
import Router from 'vue-router';

// All the views
import Home from './views/Home.vue';
import Nested from './views/Nested.vue';
import Animated from './views/Animated.vue';
import Dynamic from './views/Dynamic.vue';
import Guarded from './views/Guarded.vue';
import Login from './views/Login.vue';
import WithProps from './views/WithProps.vue';

Vue.use(Router);

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
      children: [
        {
          name: 'nested-home',
          path: 'nested',
          component: Nested
        }
      ]
    },
    {
      path: '/animated',
      component: Animated
    },
    {
      path: '/dynamic/:id',
      component: Dynamic
    },
    {
      path: '/login',
      component: Login
    },
    {
      path: '/very-secure',
      component: Guarded,
      beforeEnter: (to, from, next) => {
        let isAuthenticated;
        try {
          isAuthenticated = sessionStorage.getItem('authenticated');
        } catch (error) {
          return next({ path: '/login' });
        }

        return isAuthenticated ? next() : next({ path: '/login' });
      }
    },
    {
      path: '/with-props',
      component: WithProps,
      props: { newsletterPopup: true }
    },
    {
      path: '/lazy-loaded',
      name: 'lazyLoaded',
      // route level code-splitting
      // this generates a separate chunk (lazyLoaded.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () =>
        import(/* webpackChunkName: "lazyLoaded" */ './views/LazyLoaded.vue')
    }
  ]
});

Here is how we add the router when we are bootstraping our Vue app:

// src/main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';

new Vue({
  router,
  render: h => h(App)
}).$mount('#app');

Now let's start to dig deeper and find out what does each part of these router configurations actually do. 🧐

Essentials

Using props

Example route-config:

// src/router.js
{
  path: "/with-props",
  component: WithProps,
  props: { newsletterPopup: true }
}

A simple view that gets props from the router:

// src/views/WithProps.vue
<template>
  <div class="home">
    <h1>This is a view with props coming from the router!</h1>
    <h2>Look at that - {{ $props.newsletterPopup }}</h2>
  </div>
</template>

<script>
export default {
  props: {
    newsletterPopup: Boolean
  }
};
</script>

You might have noticed that some of these routes have names defined. So how do these work you wonder?

Named routes

A route name provides an alternative way to navigate to routes without relying on its path.

Example route-config:

// src/router.js
{
  path: "/",
  component: Home,
  children: [
    {
      name: "nested-home",
      path: "nested",
      component: Nested
    }
  ]
}

Here is how you can use it in a router-link

<router-link :to="{ name: 'nested-home' }">Nested</router-link> |

You might be thinking to yourself..."huh, router-link? 😕"

The router-link helps you with navigation, its like anchor links but with super powers.

Under the hood, it renders an anchor tag with correct href. Also, the router-link component gets automatically CSS classes when the target route is active.

It is considered a best-practice to stick to router-link over regular anchor links.

Want to know more? You can dig deeper here.

You have have noticed this router-view thing!

router-view

In simple terms, this the placeholder that gets replaced dynamically with the component that matches your route.

<router-view></router-view>

Here the official description from the Vue docs:

The router-view component is a functional component that renders the matched component for the given path.

Components rendered in router-view can also contain its own router-view, which will render components for nested paths.

Any non-name props will be passed along to the rendered component, however most of the time the per-route data is contained in the route's params.

Next, lets talk about nested routes...

Nested routes

Got a use-case where you need to nest routes? Easy!

You can define children for the route.

Example route-config:

// src/router.js
{
  path: "/",
  component: Home,
  children: [
    {
      name: "nested-home",
      path: "nested",
      component: Nested
    }
  ]
}

Here is a view that has another nested route, hence the router-view

// src/views/Home.vue
<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <HelloWorld msg="Welcome to Your Vue.js App" />
    <router-view />
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from "@/components/HelloWorld.vue";

export default {
  name: "home",
  components: {
    HelloWorld
  }
};
</script>

And the Nested view itself:

// src/views/Nested.vue
<template>
  <div class="about">
    <h1>This is a nested view, Helloww!</h1>
  </div>
</template>

What about dynamic URL segments? If I have for example user IDs or a dynamic field of some sort?

Dynamic routing & Router param

Example of a route-config with dynamic segment :id

// src/router.js
{
  path: "/dynamic/:id",
  component: Dynamic
}

You can access the dynamic param in your component like this:

// src/views/Dynamic.vue
<template>
  <div>
    <h1>This is a very dynamic page, here is the id:</h1>
    <h2 class="highlight">{{ $route.params.id }}</h2>
    <span>Its almost like magic right?</span>
  </div>
</template>

<style lang="scss" scoped>
.highlight {
  font-weight: bold;
}
</style>

This is a good place to take a break! what about standing up & stretching ? Go for it! I will be here when you come back. 👍

Advanced

Ok now that you know all the basics, let's have a look into the more advanced stuff.

Route guards

Here is how you can create protected routes that only authenticated users are allowed to see:

// src/router.js
{
  path: "/login",
  component: Login
},
{
  path: "/very-secure",
  component: Guarded,
  beforeEnter: (to, from, next) => {
    let isAuthenticated;
    try {
      isAuthenticated = sessionStorage.getItem("authenticated");
    } catch (error) {
      return next({ path: "/login" });
    }

    return isAuthenticated ? next() : next({ path: "/login" });
  }
}
// src/views/Guarded.vue
<template>
  <div class="about">
    <h1>This is a nested view, Helloww!</h1>
  </div>
</template>
// src/App.vue
methods: {
  authenticate() {
    sessionStorage.setItem("authenticated", true);
  },
  logout() {
    sessionStorage.removeItem("authenticated");
  }
}

Keep in mind that this is just a simple example, you might want to add more layers of checks in real-world applications. 😁

Wild card routes

Here is how you can add a wild card route to catch unknown routes.

{
  // will match everything
  path: '*';
  component: NotFound;
}

You can use this technique to display a "Not Found 404" page. 💯

Watch route

What if you want to react to route changes? You can add a specific watcher to the $route object.

<script>
export default {
  watch: {
    $route(to, from) {
      console.log("to", to);
      console.log("from", from);
      // react to route changes...
    }
  }
};
</script>

Since we are at it, let's talk about the route object.

The route object

Here is how the route object looks like :

interface RouteConfig = {
  path: string,
  component?: Component,
  name?: string, // for named routes
  components?: { [name: string]: Component }, // for named views
  redirect?: string | Location | Function,
  props?: boolean | Object | Function,
  alias?: string | Array<string>,
  children?: Array<RouteConfig>, // for nested routes
  beforeEnter?: (to: Route, from: Route, next: Function) => void,
  meta?: any,

  // 2.6.0+
  caseSensitive?: boolean, // use case sensitive match? (default: false)
  pathToRegexpOptions?: Object // path-to-regexp options for compiling regex
}

Want to know more? Check out the docs.

Do you happen to have a bit more special use-cases? Let's check how you can use the router options.

Router options

You can customize the router to your taste.

Here are some of the config options when initializing the router.

// src/router.js

new Router({
  mode: 'history', //  the router mode
  routes: [
      // Routes go here
  ],
  base: '/', // The base URL of the app
  linkActiveClass: 'router-link-active', // <router-link> default active class
  linkExactActiveClass: 'router-link-exact-active', // <router-link> default active class for exact matches
  scrollBehavior (to, from, savedPosition) {
    // native-like behavior when navigating with back/forward buttons
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }
  }parseQuery: q => q, // custom query string parse
  fallback: true, // whether the router should fallback to hash mode
  });

You can dig deeper by reading the documentation:

I know this has been a long tutorial, stay with me we are almost done! 😌

Router transition

Want to add transitions effects to your routed component?

Adding simple transitions in Vue is easy, just wrap your components inside the transition component.

// src/views/Animated.vue
<template>
  <transition name="fade">
    <div>
      <h1>This is a animated page, it fades away slowly...</h1>
    </div>
  </transition>
</template>


<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 2s;
}

.fade-enter,
.fade-leave-to {
  /* .fade-leave-active below version 2.1.8 */
  opacity: 0;
}
</style>

You can read more about Vue transitions & animations here.

Lazy-loading routes

Lazy-loading is a useful technique to increase the performance of your application. Here is an example:

// src/router.js
{
  path: "/lazy-loaded",
  name: "lazyLoaded",
  // route level code-splitting
  // this generates a separate chunk (lazyLoaded.[hash].js) for this route
  // which is lazy-loaded when the route is visited.
  component: () =>
    import(/* webpackChunkName: "lazyLoaded" */ "./views/LazyLoaded.vue")
}
// src/views/LazyLoaded.vue
<template>
  <h1>This is a lazy-loaded view. Performance baby!</h1>
</template>

This way, you can lazy-load routes only when they are needed. Just use the dynamic import syntax (as you can see in the src/router.js snippet) and you are good to go.

The router has different hooks that are executed in a specific order.

Understanding the sequence of these hooks is helpful. This way you can ensure that your logic is at the right place at the right time.

Here is a, poorly drawn, diagram that explains the execution order of the router hooks:

A diagram showing how the sequence in which the router hooks are called.

A couple of use cases of the router hooks:

  • Want to have globally guarded routes? Hook number 2 beforeEach that runs globally might be your best option.
  • Want to add component-specific router logic? Have a look at the hook number 5 beforeRouteEnter.

That is it, now you are a Vue router guru! ✋

Support us

Enjoyed the article? Share the summary thread on twitter.



Author image

Learn how to build scalable, fast and accessible web applications.

Follow us on Twitter

hello@nordschool.com