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 tofrom
— the current route that’s being navigated fromnext
— 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:
- navigation triggered
beforeRouteLeave
called- global
beforeEach
guard called beforeRouterUpdate
in component calledbeforeEnter
in route configs called- resolve async route components
beforeEnter
in activated components called- global
beforeResolve
guards called - navigation confirmed
- global
afterEach
hooks called - DOM updates triggered
- callbacks passed to
next
inbeforeRouteEnter
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.