Iniubong Obonguko Frontend developer, Vue ninja, code enthusiast. Learning every day.

The ultimate guide to Vue Router

11 min read 3235

Introduction

Routing in web development is a mechanism in which HTTP requests are routed to the code that handles them. In simpler terms, with a router, you determine what should happen when a user visits a certain page on your website.

In the modern age of JavaScript frameworks, routing on the web works a bit differently; you could say it’s gotten easier to implement.

In Vue, routing is handled using the Vue Router package. If your web application or website is built with Vue and contains multiple pages that users need to switch between, you definitely need Vue Router.

Goals

In this tutorial, we’re going to take a look at how to implement routing in a Vue app using Vue Router by building a mini demo application.

The demo is an application that shows information concerning breweries: their location, type, and website. We’ll be pulling data from the Open Brewery DB API.

Keep in mind we’re going to be using both the most recent versions of Vue Router and Vue in our application.

Prerequisites

This tutorial assumes that the reader has the following installed on their machine:

  • Node.js 10.6.0 or above
  • Yarn / npm
  • Vue CLI
  • VS code

Working knowledge of JavaScript and Vue is strongly recommended.

You can install the Vue CLI with the following command:

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

Getting started

We will be using the Vue CLI tool to bootstrap a new Vue project. This tool allows us not to worry about configurations when we start using our app, because we can manually select the required packages.

We made a custom demo for .
No really. Click here to check it out.

Let’s create our project!

Open up the terminal and type in the following command to create a new Vue project:

vue create breweries-app

There’ll be a prompt to pick a preset. Select Default (Vue 3) ([Vue 3] babel, eslint) get set up quickly.

Screenshot of Vue CLI presets

With the Vue CLI we can install the Vue Router package and let it set up automatically by choosing the Manually select features option, then choosing Vue Router as part of our project’s configuration. But that wouldn’t be much fun, would it?

For the sake of this tutorial we’ll learn how to manually set up and configure Vue Router ourselves.

Now, change directory to the project folder with the following command:

cd breweries-app

Then, start the project like so:

yarn serve
#OR
npm run serve

You can visit the application running on https://localhost:8080.

Screenshot of blank Vue app

Creating our views

Before we proceed to the meat of our project, we have to work on our views. Views are the pages that will be shown to users when they visit our website or application.

First, let’s open the project in your preferred code editor.

Next, change directories on the terminal to move into the src directory:

cd src

Then, run the following command to create a folder named views:

mkdir views

Create the following files: AllBreweries.vue , BreweryDetail.vue, and NotFound.vue inside of the views directory we just created.

Open up the newly created files and paste in the following code specific to each file.

First, our AllBreweries.vue page, which is the first page that will be served when a user visits our website:

<!-- Vue.Js -->
<!-- AllBreweries.vue -->
<template>
  <div class="container">
    <div class="breweries-grid">
      <div
        class="brewery"
        v-for="brewery in allBreweriesList"
        :key="brewery.id"
      >
        <div class="brewery-info">
          <h3>{{ brewery.name }}</h3>
          <p>{{ brewery.country }}</p>
          <a :href="brewery.website_url">
            {{ brewery.website_url || `Not available`}}
          </a>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { onMounted, ref } from "vue";
export default {
  setup() {
    const apiUrl = "https://api.openbrewerydb.org/breweries/";
    // We declare our list and make it reactive
    let allBreweriesList = ref([]);
    onMounted(() => {
      // We call our function here when the component is first instantiated
      fetchAllBreweries();
    });
    const fetchAllBreweries = () => {
      // Function to retrieve  a list of all breweries from the API
      fetch(apiUrl)
        .then((response) => response.json())
        .then((data) => {
          // here we set the data gotten from the API to equal our array
          allBreweriesList.value = data;
        });
    };
    return {
      allBreweriesList,
      fetchAllBreweries,
    };
  },
};
</script>
<style>
html {
  padding: 0;
  margin: 0;
}
.container {
  min-height: 100vh;
  height: 100%;
  margin: 0 auto;
  padding: 0 20px;
  margin: 0 auto;
}
.brewery {
  height: 200px;
  min-width: 280px;
  margin: 0 auto;
  border-radius: 10px;
  box-shadow: 1.5px 1.5px 11px hsla(208.4, 23.5%, 31.8%, 0.39),
    -1.5px -1.5px 11px rgba(0, 0, 0, 0);
  background-color: rgba(50, 155, 29, 0.568);
  cursor: pointer;
}
.breweries-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  grid-gap: 3rem;
  grid-auto-rows: minmax(100px, auto);
  padding: 50px 0;
  margin: 0 auto;
}
.brewery-info {
  padding-left: 20px;
}
</style>

In AllBreweries.vue, we’ve successfully retrieved data from the API, then iterated over that data to display it in the template.

Next, let’s do our BreweryDetail.vue page, which contains extra information about a specific brewery when clicked:

<!-- Vue.Js -->
<!-- BreweryDetail.vue -->
<template>
  <div class="brewery-detail">
     <ul>
        <li><strong>Name: </strong></li>
        <li><strong>Type: </strong></li>
        <li><strong>Country: </strong></li>
        <li><strong>State: </strong></li>
        <li><strong>Street: </strong>
        </li>
        <li>
          <strong>Website: </strong> 
            <a href=""></a>
        </li>
      </ul>
    </div>
</template>

Now, in our BreweryDetail.vue page, there isn’t much going on here other than some basic HTML. That’s because we’ll finish up this component bit by bit as we progress.

Last, let’s do some markup for our NotFound.vue component:

<!-- Vue.Js -->
<!-- NotFound.vue -->
<template>
  <div>
    <h1>
  Sorry, this page doesn't exist or has been moved to another location
    </h1>
  </div>
</template>

Our NotFound.vue is the page we’ll serve to our users when they visit a route that does not exist. We’ll learn more about this in a later section.

Router setup and configuration

Now, we’re at the fun part of installing and configuring Vue Router!

First, change directory back into the root of our project folder using this command:

cd ..

To install Vue Router in the root folder of our project, run the following:

yarn add [email protected]
#OR
npm install [email protected]

Next, change directory back into the src folder:

cd src

Create a folder in the src directory called router:

mkdir router

Change directory once again to move into the newly created router folder:

cd router

Finally, create a file named index.js in this directory:

touch index.js

This file will serve as our router configuration file.

Inside the newly created index.js file, paste the following code:

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import AllBreweries from "@/views/AllBreweries.vue"
const routes = [
    {
        path: "/",
        name: "AllBreweries",
        component: AllBreweries,
    },
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

Let’s dive into what our code here means.

First things first, we need to import the functions createRouter and createWebHistory from Vue Router. These functions create a history that a user can go back to, and construct a router object for Vue, respectively.

Notice how we create our routes in an array, where each route is an object with the following properties:

  • Path, the URL path where this route can be found
  • Name, an optional name to use when we link to this route
  • Component, which component to load when this route is called

We then create the router object to invoke the createRouter function, then pass in the history key values and the routes array as parameters, then export it.

Next, open up the main.js file in the the src folder and add import router from "./router" after import App from './App.vue', and .use(router) between createApp(App) and .mount('#app').

The bits of code above import Vue Router and instantiate it on the global Vue instance.

Next, open up the App.vue file in the src directory and delete all its content, leaving only the following:

<!-- Vue.Js -->
<!--App.vue -->
<template>
<!-- This serves all our pages using vue router -->
 <router-view />
</template>
<script>
export default {
  name: 'App',
}
</script>

The router-view component we added on line four is a functional component that renders the matched component for the given path.

Lazy loading routes

Let’s rewind a bit to learn an interesting routing trick called lazy loading.

Because our apps tend to get complex as they grow, our bundle size increases, which then slows down the loading time. Thankfully, Vue Router has the lazy loading feature, which enables us to put off loading specific routes until they are visited by the user.

Let’s try to implement this in our router configuration file. In router/index.js, add the following code after name: "AllBreweries",:

         component: () => import(
    /* webpackChunkName: "AllBreweries" */ '../views/AllBrewries.vue')

After you have done so, the file should look like this:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
    {
        path: "/",
        name: "AllBreweries",
         component: () => import(
    /* webpackChunkName: "AllBreweries" */ '../views/AllBrewries.vue')
    }
]
const router = createRouter({
    history: createWebHistory(process.env.BASE_URL),
    routes
})
export default router

Notice that we’ve removed our import statement that was previously at the top and replaced the component property for the route with a dynamic import statement. This statement fetches the required file when the route is visited. That’s all there is to it; we’ve implemented the lazy loading functionality.

Dynamic routing

For the app we’re building, when a user clicks on a specific brewery, it takes them to a page that contains more detailed information about that brewery. Now, we can’t manually create a route for every brewery that is listed in the API. How do we solve this?

Fortunately, Vue Router has a feature called dynamic routing, which enables us load dynamic data through our routes. Let’s see how we can take advantage of this.

First, we need to modify our router/index.js file to create the route for the BreweryDetail.vue page:

//router/index.js
...
const routes = [
    {
        path: "/",
        name: "AllBreweries",
        component: () => import(
    /* webpackChunkName: "AllBreweries" */ '../views/AllBrewries.vue')
    },
    {
        path: "/brewery/:id",
        name: "BreweryDetail",
        component: () => import(
  /* webpackChunkName: "BreweryDetail" */ '../views/BreweryDetail.vue')
    }
...

The route’s path has a dynamic :id segment called a “param.” The param is used to manipulate the state of the route’s component using whatever value is passed via :id, and can be accessed using $route.params in our template, thus making the route dynamic.

Next, let’s work on the logic behind the routing for our BreweryDetail.vue page:

<!-- Vue.Js -->
<!-- BreweryDetail.vue -->
...
<script>
import { onMounted, ref } from "vue";
import { useRoute } from "vue-router";
export default {
  setup() {
      // create object that brewery information from API will be stored in
    let breweryDetails = ref({});
    const apiUrl = "https://api.openbrewerydb.org/breweries/";
    // here we instantiate the useRoute method in our component
    const route = useRoute();
    onMounted(() => {
        // invoke the function when our component is mounted on DOM
      fetchAllBreweryDetail();
    });
    // function to fetch all brewery information
    const fetchAllBreweryDetail = () => {
     // append the route params to the url to get information on the specific brewery clicked by user
      fetch(apiUrl + route.params.id)
        .then((response) => response.json())
        .then((data) => {
          //set data gotten from API call to our breweryDetails Object
          breweryDetails.value = data;
        });
    };
    return {
      fetchAllBreweryDetail,
      breweryDetails,
    };
  },
};
</script>
...

And we can update the template section as follows:

<!-- Vue.Js -->
<!-- BreweryDetail.vue -->
<template>
  <div class="brewery-detail">
      <!-- Button users can click to back to the previous route  -->
    <button class="back-btn" @click="$router.back()">Go back</button>
      <ul>
          <!-- Up here in the template, we access the data gotten from the API -->
        <li><strong>Name: </strong>
          {{ breweryDetails.name }}
        </li>
        <li><strong>Type: </strong>
          {{ breweryDetails.type || `Not available` }}
        </li>
        <li><strong>Country: </strong>
          {{ breweryDetails.country }}
        </li>
        <li><strong>State: </strong>
          {{ breweryDetails.state }}
        </li>
        <li><strong>Street: </strong>
          {{ breweryDetails.street || `Not available` }}
        </li>
        <li>
          <strong>Website: </strong> 
            <a :href="breweryDetails.website_url">
            {{ breweryDetails.website_url || `Not Available` }}
            </a>
        </li>
      </ul>
    </div>
</template>

Here, our code fetches the data from the breweryDetails object and updates it on our template.

If you’ve noticed, we’ve added a nice little button our users can click on to go back to the previously visited route. It works just like hitting the back button on the browser.

Navigating between routes

To make it possible for users to navigate to a brewery’s specific page, we’ll modify the code in the template section of AllBreweries.vue.

Instead of our regular HTML anchor tags, Vue Router has its own router-link component that we can pass to the to prop, which accepts an object with a bunch of key or value pairs.

There are two ways to navigate between routes using the router-link component: via the path property and named routes.

Path property

In using the path property method, we only need to pass in the URL path we want users to access when they click the link. Then, append the ID retrieved from the API and set it as the param value.

The URL path is always as same as defined as in the router configuration file. Your code should look like the following (note lines 12 and 14):

<!-- Vue.Js -->
<!-- AllBreweries.vue -->
<template>
  <div class="container">
    <div class="breweries-grid">
      <div
        class="brewery"
        v-for="brewery in allBreweriesList"
        :key="brewery.id"
      >
        <div class="brewery-info">
          <router-link :to="'/brewery/' + brewery.id">
          <h3>{{ brewery.name }}</h3>
          </router-link>
          <p>{{ brewery.country }}</p>
          <a :href="brewery.website_url">
            {{ brewery.website_url || `Not available`}}
          </a>
        </div>
      </div>
    </div>
  </div>
</template>

Named routes

With the named routes method, we are passing in the name key that has access to the name property as a value of each of the routes we created.

We also pass in the the params key, which accepts an object. Inside the object we have the id key, which we use to set the value of the param equal to the ID retrieved from the API.

This is what your code should look like (note lines 12, 13, and 15):

<!-- Vue.Js -->
<!-- AllBreweries.vue -->
<template>
  <div class="container">
    <div class="breweries-grid">
      <div
        class="brewery"
        v-for="brewery in allBreweriesList"
        :key="brewery.id"
      >
        <div class="brewery-info">
          <router-link 
            :to="{name: 'BreweryDetail', params: {id: brewery.id}}">
            <h3>{{ brewery.name }}</h3>
          </router-link>
          <p>{{ brewery.country }}</p>
          <a :href="brewery.website_url">
            {{ brewery.website_url || `Not available`}}
          </a>
        </div>
      </div>
    </div>
  </div>
</template>

Both methods work, but in a scenario where we have to change the name of the URL path we want users to visit, we have to manually change it at each instance.

Now, this isn’t much of a problem here because it’s used in just one place, but think of all the stress we’d have to go through if we had used that URL path at a thousand instances.

However, if we go with the named routes method, we will only need to change the URL path at one instance: our router file configuration. Sounds easy right?

Now that we’ve covered the benefits of the named route method, we’ll be proceeding with that in this tutorial.

Handling 404 errors

Say a user visits a route that we didn’t create earlier in our router configuration file. Our app will load, but without a component. We need a way to tell our router what to do when this scenario occurs.

To implement this feature, we’ll need to modify our router configuration file by adding the following code to our index.js file:

    {
        path: "/:catchAll(.*)",
        component: () => import(
        /* webpackChunkName: "NotFound" */ '../views/NotFound.vue')
    },
...

Vue Router uses a custom regular expression to achieve this. :catchAll is the dynamic segment, and (.*) is a regular expression Vue Router uses to evaluate if the route being visited has been defined in our router’s configuration file. If it doesn’t exist, our NotFound.vue component gets rendered on the screen instead.

Route transitions

An interesting way to add a nice touch to our user experience is through route transitions.

To add this to our application, all we need is to wrap the <router-view/> element tags with the v-slot attribute around Vue Router’s custom transition element tags.

Next, in between the transition tags, we add Vue’s component element and bind its is attribute to the value of the component we get from the Vue slot.

We also pass to it a key attribute with the value of $route.path (the current path) to ensure that the transition works correctly, and that it will still destroy and recreate all the pages when the route changes.

In the transition element, we assign the name attribute the value of fade, which determines the name of the CSS classes that will be applied to the element. The transition element has a mode attribute that instructs Vue Router how to handle the transition between pages.

Next, in our styles section, we can define the CSS properties that will apply to the page when the route changes.

Here’s how we can add this to our application:

<!-- Vue.Js -->
<!-- App.vue -->
<template>
<!-- This serves all our pages using vue router -->
<router-view v-slot="{ Component }">
  <transition name="fade" mode="out-in">
    <component :is="Component" :key="$route.path"/> 
  </transition>
</router-view>
</template>

<style>
.fade-enter-active, .fade-leave-active {
  transition: opacity .4s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>

Conclusion

Using Vue Router in our Vue applications is quite easy and straightforward. In this article we have learned how to add page routing functionalities and also utilize some of Vue Router’s features that make it simple to give our users a wonderful experience as they browse through our application.

For more information on Vue Router, I recommend reading through their documentation.

You can find the source code for this tutorial here.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Iniubong Obonguko Frontend developer, Vue ninja, code enthusiast. Learning every day.

Leave a Reply