If you are using Vue, the odds are you will need to deal with the Vue router. Let's go through all the common use-cases you will need! 👌
In this tutorial, we will cover the most essential router concepts as well as more advanced patterns such as guarded routes & animated routes.
Are you ready? Let's do this! 💪
Let's have a look at the bigger picture first and then dig deeper.
I have created a small vue project to demonstrate different functionalities of the Vue Router. The project has a standard setup using the vue-cli.
├── README.md
├── babel.config.js
├── package.json
├── postcss.config.js
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ ├── main.js
│ ├── router.js
│ └── views
│ ├── Animated.vue
│ ├── Dynamic.vue
│ ├── Guarded.vue
│ ├── Home.vue
│ ├── LazyLoaded.vue
│ ├── Login.vue
│ ├── Nested.vue
│ └── WithProps.vue
└── yarn.lock
We will be dealing mostly with the router.js but also different views.
Here is how the main router configurations look like:
import Vue from 'vue';
import Router from 'vue-router';
// All the views
import Home from './views/Home.vue';
import Nested from './views/Nested.vue';
import Animated from './views/Animated.vue';
import Dynamic from './views/Dynamic.vue';
import Guarded from './views/Guarded.vue';
import Login from './views/Login.vue';
import WithProps from './views/WithProps.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home,
children: [
{
name: 'nested-home',
path: 'nested',
component: Nested
}
]
},
{
path: '/animated',
component: Animated
},
{
path: '/dynamic/:id',
component: Dynamic
},
{
path: '/login',
component: Login
},
{
path: '/very-secure',
component: Guarded,
beforeEnter: (to, from, next) => {
let isAuthenticated;
try {
isAuthenticated = sessionStorage.getItem('authenticated');
} catch (error) {
return next({ path: '/login' });
}
return isAuthenticated ? next() : next({ path: '/login' });
}
},
{
path: '/with-props',
component: WithProps,
props: { newsletterPopup: true }
},
{
path: '/lazy-loaded',
name: 'lazyLoaded',
// route level code-splitting
// this generates a separate chunk (lazyLoaded.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "lazyLoaded" */ './views/LazyLoaded.vue')
}
]
});
Here is how we add the router when we are bootstraping our Vue app:
// src/main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
new Vue({
router,
render: h => h(App)
}).$mount('#app');
Now let's start to dig deeper and find out what does each part of these router configurations actually do. 🧐
Example route-config:
// src/router.js
{
path: "/with-props",
component: WithProps,
props: { newsletterPopup: true }
}
A simple view that gets props from the router:
// src/views/WithProps.vue
<template>
<div class="home">
<h1>This is a view with props coming from the router!</h1>
<h2>Look at that - {{ $props.newsletterPopup }}</h2>
</div>
</template>
<script>
export default {
props: {
newsletterPopup: Boolean
}
};
</script>
You might have noticed that some of these routes have names defined. So how do these work you wonder?
A route name provides an alternative way to navigate to routes without relying on its path.
Example route-config:
// src/router.js
{
path: "/",
component: Home,
children: [
{
name: "nested-home",
path: "nested",
component: Nested
}
]
}
Here is how you can use it in a router-link
<router-link :to="{ name: 'nested-home' }">Nested</router-link> |
You might be thinking to yourself..."huh, router-link? 😕"
The router-link helps you with navigation, its like anchor links but with super powers.
Under the hood, it renders an anchor tag with correct href. Also, the router-link component gets automatically CSS classes when the target route is active.
It is considered a best-practice to stick to router-link over regular anchor links.
Want to know more? You can dig deeper here.
You have have noticed this router-view thing!
In simple terms, this the placeholder that gets replaced dynamically with the component that matches your route.
<router-view></router-view>
Here the official description from the Vue docs:
The router-view component is a functional component that renders the matched component for the given path.
Components rendered in router-view can also contain its own router-view, which will render components for nested paths.
Any non-name props will be passed along to the rendered component, however most of the time the per-route data is contained in the route's params.
Next, lets talk about nested routes...
Got a use-case where you need to nest routes? Easy!
You can define children for the route.
Example route-config:
// src/router.js
{
path: "/",
component: Home,
children: [
{
name: "nested-home",
path: "nested",
component: Nested
}
]
}
Here is a view that has another nested route, hence the router-view
// src/views/Home.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
<router-view />
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from "@/components/HelloWorld.vue";
export default {
name: "home",
components: {
HelloWorld
}
};
</script>
And the Nested view itself:
// src/views/Nested.vue
<template>
<div class="about">
<h1>This is a nested view, Helloww!</h1>
</div>
</template>
What about dynamic URL segments? If I have for example user IDs or a dynamic field of some sort?
Example of a route-config with dynamic segment :id
// src/router.js
{
path: "/dynamic/:id",
component: Dynamic
}
You can access the dynamic param in your component like this:
// src/views/Dynamic.vue
<template>
<div>
<h1>This is a very dynamic page, here is the id:</h1>
<h2 class="highlight">{{ $route.params.id }}</h2>
<span>Its almost like magic right?</span>
</div>
</template>
<style lang="scss" scoped>
.highlight {
font-weight: bold;
}
</style>
This is a good place to take a break! what about standing up & stretching ? Go for it! I will be here when you come back. 👍
Ok now that you know all the basics, let's have a look into the more advanced stuff.
Here is how you can create protected routes that only authenticated users are allowed to see:
// src/router.js
{
path: "/login",
component: Login
},
{
path: "/very-secure",
component: Guarded,
beforeEnter: (to, from, next) => {
let isAuthenticated;
try {
isAuthenticated = sessionStorage.getItem("authenticated");
} catch (error) {
return next({ path: "/login" });
}
return isAuthenticated ? next() : next({ path: "/login" });
}
}
// src/views/Guarded.vue
<template>
<div class="about">
<h1>This is a nested view, Helloww!</h1>
</div>
</template>
// src/App.vue
methods: {
authenticate() {
sessionStorage.setItem("authenticated", true);
},
logout() {
sessionStorage.removeItem("authenticated");
}
}
Keep in mind that this is just a simple example, you might want to add more layers of checks in real-world applications. 😁
Here is how you can add a wild card route to catch unknown routes.
{
// will match everything
path: '*';
component: NotFound;
}
You can use this technique to display a "Not Found 404" page. 💯
What if you want to react to route changes? You can add a specific watcher to the $route object.
<script>
export default {
watch: {
$route(to, from) {
console.log("to", to);
console.log("from", from);
// react to route changes...
}
}
};
</script>
Since we are at it, let's talk about the route object.
Here is how the route object looks like :
interface RouteConfig = {
path: string,
component?: Component,
name?: string, // for named routes
components?: { [name: string]: Component }, // for named views
redirect?: string | Location | Function,
props?: boolean | Object | Function,
alias?: string | Array<string>,
children?: Array<RouteConfig>, // for nested routes
beforeEnter?: (to: Route, from: Route, next: Function) => void,
meta?: any,
// 2.6.0+
caseSensitive?: boolean, // use case sensitive match? (default: false)
pathToRegexpOptions?: Object // path-to-regexp options for compiling regex
}
Want to know more? Check out the docs.
Do you happen to have a bit more special use-cases? Let's check how you can use the router options.
You can customize the router to your taste.
Here are some of the config options when initializing the router.
// src/router.js
new Router({
mode: 'history', // the router mode
routes: [
// Routes go here
],
base: '/', // The base URL of the app
linkActiveClass: 'router-link-active', // <router-link> default active class
linkExactActiveClass: 'router-link-exact-active', // <router-link> default active class for exact matches
scrollBehavior (to, from, savedPosition) {
// native-like behavior when navigating with back/forward buttons
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}‚
parseQuery: q => q, // custom query string parse
fallback: true, // whether the router should fallback to hash mode
});
You can dig deeper by reading the documentation:
I know this has been a long tutorial, stay with me we are almost done! 😌
Want to add transitions effects to your routed component?
Adding simple transitions in Vue is easy, just wrap your components inside the transition component.
// src/views/Animated.vue
<template>
<transition name="fade">
<div>
<h1>This is a animated page, it fades away slowly...</h1>
</div>
</transition>
</template>
<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 2s;
}
.fade-enter,
.fade-leave-to {
/* .fade-leave-active below version 2.1.8 */
opacity: 0;
}
</style>
You can read more about Vue transitions & animations here.
Lazy-loading is a useful technique to increase the performance of your application. Here is an example:
// src/router.js
{
path: "/lazy-loaded",
name: "lazyLoaded",
// route level code-splitting
// this generates a separate chunk (lazyLoaded.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "lazyLoaded" */ "./views/LazyLoaded.vue")
}
// src/views/LazyLoaded.vue
<template>
<h1>This is a lazy-loaded view. Performance baby!</h1>
</template>
This way, you can lazy-load routes only when they are needed. Just use the dynamic import syntax (as you can see in the src/router.js snippet) and you are good to go.
The router has different hooks that are executed in a specific order.
Understanding the sequence of these hooks is helpful. This way you can ensure that your logic is at the right place at the right time.
Here is a, poorly drawn, diagram that explains the execution order of the router hooks:
A couple of use cases of the router hooks:
That is it, now you are a Vue router guru! ✋
Enjoyed the article? Share the summary thread on twitter.