Morden apps allow us to do complex things. Because of that, it is rare that you can do everything on a single page. Instead, you have to navigate through many pages and menus. With Vue, we can do that with the Vue Router, a powerful extension to Vue. In this tutorial, we dive right into that.
This tutorial explains everything you need to know about the Vue router. To go straight to the point, we assume you are already familiar with Vue.js. However, if this is your first time, you can head to the Getting Started with Vue.js guide.
Why Vue Router?
Before we start exploring the technicalities, we should understand why we are doing this. After all, what problem are we solving?
In a traditional website, each page is a separate HTML file in the root of your website. This means that, if you want to add a new page, you just create a new HTML file. Think about a website that has three main functions: a home page, a contact page, and a make-an-order page. You can get away pretty easily with three files: index.html
, contacts.html
, and order.html
.
Whenever you navigate from one page to the other, you need to make an HTTP Request to the server. Your browser does that for you, but still, it takes time to reach the server and download the new page every single time. This system certainly works, but it has its limits.
When you create an app with Vue.js, you don’t have that possibility. In fact, the app runs in the frontend – inside the user’s browser. This means you don’t have the mechanism of asking for a different page to the backend. Instead, you should load all the pages beforehand, and then switch between one another in the frontend.
If your app has three URLs: /
, /contacts
, and /order
, you can use the Vue router to navigate between them inside Vue.
How to Use Vue.js Router
Installing the Router
As always with modern JavaScript application, the first thing you need to do is installing the module, in this case, router. Easy enough, we can do it with the following command (use --save
to add it to package.json).
npm install --save vue-router
This extension of Vue is so important that it has its own dedicated sub-website at router.vuejs.org.
Once that you have finished the installation, you can include it in your main.js file that loads Vue. Originally, in that file, we loaded only Vue.js, but now we want to load Vue Router and tell our Vue instance to use it. This is also very easy to do.
import Vue from 'vue'
// We import the vue router here
import VueRouter from 'vue-router';
import App from './App.vue'
// We tell Vue to use it for our app here
Vue.use(VueRouter);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
As simple as that, we are good to go. Now we only need to configure routes.
Routes
Routes are indeed paths inside your application. In other words, they are a sort of configuration that tells Vue.js what component or page to load when the user navigates to a specific path, such as /
, /contacts
, or /order/12345
.
To represent routes, we have to create a Vue Router. In fact, so far we only loaded the module inside our app: we enabled the capability, but we are not using it.
To create the router, I recommend creating a router.js
file in /src
, the same folder where your main.js
is. Inside here, you need to create your router and export it. As part of this, you can define the routes in a dedicated array.
import VueRouter from 'vue-router';
// Some components that we have in our app
imort MyOrderComponent from './components/order';
import MyContactsComponent from './components/contacts';
const routes = [
{ path: '/order', component: MyOrderComponent },
{ path: '/contacts', component: MyContactsComponent },
];
export default new VueRouter({
routes,
});
As you can see, each router has a path and a component. We are effectively telling our router to load that specific component whenever the user browses to the specific path. Hence, that component represents the page itself.
Now that we have defined the router, we can inject it into our Vue instance inside main.js
.
// ... other imports ...
import router from './router';
// ... other code ...
new Vue({
render: h => h(App),
// Here we use the router
router,
}).$mount('#app')
Router View
Now that we have defined the way our routes are, we might expect them to work, and yet they won’t. That’s because we have defined when to load what, but we have not defined where to load it. We can do that with the router view.
The Router View is a special Vue component that you can use inside your app. Then, the router will place in its place the component loaded with the path. When you change the path, the component will be replaced with the new one from the new path. Inside the HTML of your App.vue
, you only need to put this somewhere:
<router-view></router-view>
For example, you could do like this:
<template>
<div id="app">
<app-header/>
<div id="main" class="container">
<!-- Here is the router view -->
<router-view></router-view>
</div>
<app-footer/>
</div>
</template>
Now you may want to link between pages. With traditional websites, you do that with the A tag with some HREF attribute. Instead, here we use router links with a TO property.
<router-link to="/order">Make an order</router-link>
Views
When you define the component to load inside routes, it is tempting to pick any component you want. Don’t do that, your application scalability will soon suffer. Instead, you should create a special type of component, the view.
In reality, this is just a component like any other but has the purpose of representing an entire page. As a best practice, you should not put such component inside the /src/components
folder, but instead have a dedicated /src/views
folder, where all your views reside.
Dynamic Routes and Parameters
Sometimes, having a static path is not enough. For example, you don’t want the user to see the details about an order, but about a specific order. In that case, you may have the order’s ID inside the URL, but how do you extrapolate this information?
The first thing we want to understand is how to actually represent this information in the path. Vue Router offers us the possibility to define parameters for routes with colon and parameter’s name. If we want to give our parameter the name “id”, we use :id
in the route. So, for example:
const routes [
{ path: '/order/:id', component: MyOrderComponent },
// ... other routes ...
];
Now, all paths that have /order/
followed by something will match that route. Things like /order/123
, /order/af01srwdasw
, and similar will all be rendered with MyOrderComponent
.
The next step is how do we fetch this information, inside the component? Easy enough, all information is easily exposed inside $route.params
, followed by the variable name. In our example, ID will be $route.params.id
. You can access them easily inside the JavaScript of your component through this (i.e. this.$route.params.id
).
If you want, you can provide default values when defining the route. Simply specify the parameter name and the desired default value.
const routes [
{ path: '/order/:id', component: MyOrderComponent, id: '12345' },
// ... other routes ...
];
Advanced Vue.js Router
Watching Routes
Sometimes, you may want to trigger some specific behavior when the user navigates from a route to another. This is possible thanks to the $watch
property on your component. It allows us to watch specific properties, and trigger some code when they change from a value to another. Simply define the name of the property you want to watch (in this case $route
) inside the $watch
object, which you define next to methods.
export default {
// ... other code from the component ...
methods: {
// ... methods ...
},
watch: {
// Watch $route, to is new value and from old value
$route(to, from) {
console.log('Old route was: ');
console.log(from);
console.log('And now new route is: ');
console.log(to);
}
}
Wildcard Routes
Rarely, you may want to use some wildcard routes to match everything or almost everything. In fact, you generally want to have a route that matches everything as a catch-all, some sort of not found route. Simply use an asterisk inside your path to match everything.
const routes [
// Match everything
{ path: '*', },
// Match everything starting with user
{ path: 'user*' }
];
In this very example, the second route will never match. In fact, routes are processed top-down, and as soon as a match is found the router uses that one. The first route will match in all the cases, so routes after that will never be processed.
Nested Routes
Your components may also contain another <router-view></router-view>
tag. This can be helpful when you have to represent something inside of something else. For example, you may load the profile page of the user, which in turn may contain the password reset page, the privacy settings page, and so on.
You can manage those sub-pages with a Vue Router as well, and it is very simple. First, you have to add the Router View component inside your component (and not only inside your App.vue
). Then, you can define children in routes inside router.js
.
Going back to the example of User with several sub-pages, you can create an UserComponent
that has the following template.
<template>
<div class="user">
<h1>User #{{ $route.params.id }}</h1>
<router-view></router-view>
</div>
</template>
Then, we can configure our routes to properly inject another component inside that nested router view. That’s where the children
property comes in. It is another array structured in the same way as the one where you define the main routes.
const routes = [
{ path: '/order/:id', component: OrderComponent },
{ path: '/contacts', component: Contacts },
{
path: '/user/:id',
component: User,
children: [
{ path: 'profile', component: UserProfile },
{ path: 'reset-password', component: UserResetPassword },
]
}
];
Effectively, we are creating two sub-routes to /user/:id/profile
and /user/id:/reset-password
. Those will load the User
component, and then they will load either UserProfile
or UserResetPassword
inside its router view.
Programmatic Navigation
Sometimes, using links inside the template is not enough. You may want to navigate to specific pages from the JavaScript logic. You can do that with the push()
method on the router object. As a parameter, you only need to provide the path literal. You can access the router of the current Vue instance with this.$router
.
export default {
// ... other code ...
methods: {
goToHome() {
this.$router.push('/home');
},
},
};
In addition, you can go back or forward in your history with the go()
method. Use a positive number to move forward of N pages, and a negative number to go backward of N pages.
this.$router.go(-2);
this.$router.go(2);
In this example, we go back of two pages in history, and then forward of 2, effectively returning on the current page. If you don’t have two pages to go back or go forward, it will fail silently.
Vue Router for Full Stack Developers
Vue is a powerful tool at the service of full-stack developers, and so is the Vue router. However, all these concepts will hardly stick in your brain if you don’t put them to use – right now! That’s why we are providing you with this free exercise you can try out. In fact, this exercise and this tutorial are part of a broader Full Stack Development course (entirely free).
As part of this course, we are doing some exercise for everything that we learn, and Vue router makes no exception. Even if you are not following the course, you should try out the exercise because it is simply the best way to remember all these concepts.
In case you are curious, you can check our example Pretend Bakery store website that we are doing as part of these tutorials. The code is open on GitHub.com at alessandromaggio/full-stack-course.
The Assigment
In our previous tutorial, we scaffolded our app and created a sample Order page. That’s without any router, inside our default app. Today, we take things one step further.
For this assignment, you should install the router and set it up correctly, and have three separate views: one home, that should be the catch-all, one for contacts, and one for the order (the one from the previous tutorial). Header and footer should be provided by the App, while the views should take care of all the rest. The order view should take an ID as a parameter, which defaults to none
.
For the sake of this tutorial, you can just put custom headers in each of your views and nothing more than that.
Try to do this assignment on your own before checking the solution, which is below. Only by banging your head on the wall a few times, you will remember everything, so if you are stuck try harder a few times before going to the solution.
Okay, now time to check out the solution.
The Solution
Hopefully, by now you have tried the exercise and you have succeeded. Below you can find the solution, but you can also take a look at the full code of it in this commit on GitHub.com.
The first thing we have to do is installing the router. We can get away with that with npm install --save vue-router
, and you can see the result of that inside package.json
and package-lock.json
.
Then, we can add the /src/views folder, and create in there three components: Order.vue
. Contacts.vue
, and Home.vue
. Both the Contacts and the Home pages will look something like this:
<template>
<div>
<h2>The home...</h2>
</div>
</template>
<script>
export default {
name: 'Home',
}
</script>
Nothing too fancy, we are learning about routes and not about the tiny details of components. The order page is slightly different as it contains another component, but nothing too bad.
<template>
<div>
<h2>Order #{{ $route.params.id }}</h2>
<order-form/>
</div>
</template>
<script>
import OrderForm from '../components/OrderForm.vue';
export default {
name: 'Order',
components: {
OrderForm,
},
}
</script>
We can now predispose our App.vue
to host our routes, and to do that we need to add a router-view inside its template.
<template>
<div id="app">
<app-header/>
<div id="main" class="container">
<router-view></router-view>
</div>
<app-footer/>
</div>
</template>
Now, we can finally take care of creating the routes. We do that in a new file that we create, router.js
. Guess what, that file must export the router itself, so it goes like this:
import VueRouter from 'vue-router';
import Contacts from './views/Contacts';
import Home from './views/Home';
import Order from './views/Order';
const routes = [
{ path: '/order/:id', component: Order, id: 'none' },
{ path: '/contacts', component: Contacts },
{ path: '*', component: Home },
];
export default new VueRouter({
routes,
});
Finally, we can import it and use inside main.js
.
import Vue from 'vue'
import VueRouter from 'vue-router';
import App from './App.vue'
import router from './router';
Vue.use(VueRouter);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router,
}).$mount('#app')
And this is it! You have now implemented your application using Vue router!
In Conclusion
With the Vue router, you can now create paths inside your front-end application. This is great, and it enables you to create an app that works offline as well, at least partly. You now know most of the things you need to write a functional Vue.js app, but we are still far from finishing.
In fact, we need to learn about Vue slots to make our app even more scalable. Furthermore, at some point, you may have the problem of managing state (data transfer) across different components. In that case, you need to use Vuex, the Vue store. In case you are up for a secondary mission from our Full Stack Development course, read this tutorial on Vuex before continuing. That’s a bonus!