To create a single-page app with Vue.js, we have to add a router library to route URLs to our components.
Vue.js has a Vue Router routing library made specifically to handle this routing.
In this article, we'll take a look at how to use the Vue Router in our Vue.js app.
Getting Started
We can get started by including Vue and Vue Router scripts in our app's page. Then, we have to add a div
to house our Vue app. Also, we have to include the router-view
component to view the route's content.
To add links, we add router-link
components with the path that we want to go to.
To do all that, we write the following:
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-link to="/foo">Foo</router-link>
<router-link to="/bar">Bar</router-link>
<div>
<router-view></router-view>
</div>
</div>
<script src="./index.js"></script>
</body>
</html>
In the code above, we have the script
tags for Vue and Vue Router in the head
tag. Then, we have the div
with ID app
to house the Vue app.
The router-view
is showing our content.
Inside the div
, we have the router-link
components with the to
prop to pass in the path that we want the links to go to.
router-link
is a component that comes with Vue Router.
Finally, we have our index.js
script file. In there, we'll add our code for the route components and do the routing.
In index.js
, we have:
const Foo = { template: "<div>foo</div>" };
const Bar = { template: "<div>bar</div>" };
const routes = [
{ path: "/foo", component: Foo },
{ path: "/bar", component: Bar }
];
const router = new VueRouter({
routes
});
const app = new Vue({
router,
el: "#app"
});
In the code above, we have the Foo
and Bar
routes to show 'foo' and 'bar' respectively.
Then, we use the routes
array to map the components to the paths which we want to map the routes to.
Next, we created a new instance of VueRouter
with an object that has our routes
in it.
Then, we created a new Vue
instance with the router
object and an el
property with the #app
div that we added to house our app.
Once we did all that, we should have links on the top of the page that show 'Foo' and 'Bar' links respectively. When we click them, we'll see 'foo' and 'bar' respectively.
Dynamic Route Matching
To map routes to a dynamic URL parameter, we can add a placeholder for it by creating a name and adding a colon before it.
Then, in our Vue
instance, we can watch the $route
object for changes in the parameter and run code accordingly.
For instance, we can write the following code to use route parameters in our routes:
index.js
:
const User = {
template: "<div>User {{id}}</div>",
data() {
return {
id: undefined
};
},
watch: {
$route(to, from) {
this.id = to.params.id;
}
}
};
const routes = [
{ path: "/user/:id", component: User }
];
const router = new VueRouter({
routes
});
const app = new Vue({
router,
el: "#app"
});
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-link to="/user/1">User 1</router-link>
<router-link to="/user/2">User 2</router-link>
<router-view></router-view>
</div>
<script src="./index.js"></script>
</body>
</html>
In the code above, we have:
watch: {
$route(to, from) {
this.id = to.params.id;
}
}
to watch for URL parameter changes. We get the :id
route parameter by using to.params.id
.
Then, we set that to this.id
so that we can use it in our template, which is:
<div>User {{id}}</div>
To define the routes, we have:
const routes = [
{ path: "/user/:id", component: User }
];
The :id
part of the string is the URL parameter placeholder.
In index.html
, we have 2 router-link
components:
<router-link to="/user/1">User 1</router-link>
<router-link to="/user/2">User 2</router-link>
When we click them, we should see id
in our template update as the :id
route parameter is changing.
Catch-all / 404 Not Found Route
We can use the *
sign as a wildcard character.
To use it, we can write:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-view></router-view>
</div>
<script src="./index.js"></script>
</body>
</html>
index.js
const NotFound = {
template: "<div>not found</div>"
};
const routes = [{ path: "*", component: NotFound }];
const router = new VueRouter({
routes
});
const app = new Vue({
router,
el: "#app"
});
In the code above, we have the NotFound
component, which is the component for our catch-all route since we have:
{ path: "*", component: NotFound }
in the routes
array.
Therefore, when we go to any URL, we'll see 'not found' displayed.
Nested Routes
We can nest routes by adding a children
property to our route entries with our child routes.
For example, we can write the following to create our nested routes:
index.js
:
const User = {
template: `<div>
User {{id}}
<router-view></router-view>
</div>`,
data() {
return {
id: undefined
};
},
watch: {
$route(to, from) {
this.id = to.params.id;
}
}
};
const Profile = {
template: `<div>Profile</div>`
};
const routes = [
{
path: "/user/:id",
component: User,
children: [
{
path: "profile",
component: Profile
}
]
}
];
const router = new VueRouter({
routes
});
const app = new Vue({
router,
el: "#app"
});
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-link to="/user/1">User</router-link>
<router-link to="/user/1/profile">Profile</router-link>
<router-view></router-view>
</div>
<script src="./index.js"></script>
</body>
</html>
The difference between the nested route example above and the earlier examples is that we have the following routes
definition:
const routes = [
{
path: "/user/:id",
component: User,
children: [
{
path: "profile",
component: Profile
}
]
}
];
The children
property is used to nest our child routes.
In the template for the User
component, we have the router-view
added as follows to display items in child routes:
<div>
User {{id}}
<router-view></router-view>
</div>
We also have the following router-link
components:
<router-link to="/user/1">User</router-link>
<router-link to="/user/1/profile">Profile</router-link>
Multiple Router Views
In order to have multiple router-view
components in the same app, we have to name them.
We can define our routes as follows and put them in their own router-view
:
index.js
:
const Foo = {
template: `<div>foo</div>`
};
const Bar = {
template: `<div>bar</div>`
};
const Baz = {
template: `<div>baz</div>`
};
const router = new VueRouter({
routes: [
{
path: "/",
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
});
const app = new Vue({
router,
el: "#app"
});
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>
</div>
<script src="./index.js"></script>
</body>
</html>
In the code above, we have the router-view
named a
and router-view
named b
defined as in index.html
:
<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>
Then, in our routes
definition in index.js
, we have:
components: {
default: Foo,
a: Bar,
b: Baz
}
to map the router-view
with no name to the Foo
component, the a
router-view
to the Bar
component and the b
router-view
to the Baz
component.
Then we should see:
foo
bar
baz
displayed on the screen.
Navigation Guards
From the official Vue Router docs:
Navigation guards provided by vue-router are primarily used to guard navigations either by redirecting it or canceling it.
As such, we can add navigation guards to our routes to watch for route changes and do something before it's complete.
Enter/leave navigation guards won't trigger during params or query changes.
Global Navigation Guards
We can define a global before guard by attaching a route change listener to our router
.
To add a global navigation guard, we can write the following:
index.js
:
const Foo = {
template: `<div>foo</div>`
};
const Bar = {
template: `<div>bar</div>`
};
const Login = {
template: `<div>login</div>`
};
const router = new VueRouter({
routes: [
{
path: "/foo",
component: Foo
},
{
path: "/bar",
component: Bar
},
{
path: "/login",
component: Login
}
]
});
router.beforeEach((to, from, next) => {
if (!localStorage.getItem("authToken") && to.path !== "/login") {
return next("/login");
}
next();
});
const app = new Vue({
router,
el: "#app"
});
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-link to="/foo">Foo</router-link>
<router-link to="/bar">Bar</router-link>
<router-view></router-view>
</div>
<script src="./index.js"></script>
</body>
</html>
In the code above, we have:
router.beforeEach((to, from, next) => {
if (!localStorage.getItem("authToken") && to.path !== "/login") {
return next("/login");
}
next();
});
which is our global navigation guard that runs before navigation begins as indicated by the beforeEach
call.
We check that the path that we're going to isn't /login
with:
to.path !== "/login"
Then, if localStorage.getItem("authToken")
is false, we go to the /login
route by calling next('./login)
. Otherwise, we proceed by calling next()
.
Per-Route Guard
In a similar way, we can define per-route guards as follows:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
These are called before navigation is done and only run when we try to go to the /foo
route.
In-Component Guards
We can define in-components in our component as follows:
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
//...
},
beforeRouteUpdate (to, from, next) {
//...
},
beforeRouteLeave (to, from, next) {
//...
}
}
We have 3 guards. They are:
*beforeRouteEnter
- it's called before the route renders the component
*beforeRouteUpdate
- it's called when a route that renders the component has changed, including parameter changes.
*beforeRouteLeave
- it's called when the rendered route is about to be navigated away from.
Transitions
We can add transition
components like any other component to add transitions to router-view
.
To add transitions to a Vue app, we can use the transition
component and some simple CSS:
styles.css
:
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.9s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
index.js
:
const Foo = {
template: `<div>foo</div>`
};
const Bar = {
template: `<div>bar</div>`
};
const router = new VueRouter({
routes: [
{
path: "/foo",
component: Foo
},
{
path: "/bar",
component: Bar
}
]
});
const app = new Vue({
router,
el: "#app"
});
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div id="app">
<router-link to="/foo">Foo</router-link>
<router-link to="/bar">Bar</router-link>
<transition name="fade">
<router-view></router-view>
</transition>
</div>
<script src="./index.js"></script>
</body>
</html>
In the code above, we added the styles in styles.css
to create our CSS route transition effects. We just changed the opacity for a short moment to add the fade effect.
Then, we added the transition
component with the name
attribute set to fade
so that we can use the classes with the fade-
prefix in styles.css
to style the transition effects.
In the end, when we click on the router-link
, we'll see the fade effect.
Conclusion
We can use Vue Router to map URL paths to components.
To get route params, we watch the $route
object in our components.
We can also add nested routes by adding a children
property with nested routes.
Also, we can add a components
property to our route and name our router-view
to add multiple router-view
.
To intercept navigation and do something, we can add navigation guards for various stages of navigation.
Finally, we can use the transition
component with some CSS to create route transition effects.
Before deploying your commercial or enterprise Vue apps to production, make sure you are protecting their code against reverse-engineering, abuse, and tampering by following this tutorial.