
A Full Day of Vue.js

05. Routing & Vue Router

Vue Router
Vue
Vuex
Vue Test Utils
Routing
Routing often refers to splitting an applications UI based on rules derived from the browser URL.
Routing

https://www.twitter.com/
Routing

https://www.twitter.com/{userName}

Dynamic route
Routing
https://www.twitter.com/{userName}
protocol
hostname
pathname
Routing
Routing allows us to...
- Refresh a page and keep our location in an app
- Bookmark a URL and share it with others
- Maintain browser back/forward history functionality
Routing
Two main ways to achieve routing in a web application
Server Side Routing
Client Side Routing
The client (i.e. the browser) makes a request to the server on every URL change.
The client only makes a request to the server upon initial page-load.
First Meaningful Paint
Consistent SEO performance
Faster routing after initial load
Smooth transitions and changes
2
1
Routing
Single-page applications
(SPAs)
Client side apps are often labelled as...


/charizard
/blastoise
/venusaur
Routing
We'll build the Pokemon Routing app with:
1. Custom client-side routing
2. Vue Router
Routing - Custom Client-Side Router
Starting Point

App
CharizardCard
Routing - Custom Client-Side Router
Starting Point
<template>
<div class="card card--charizard has-text-weight-bold has-text-white">
<div class="card-image">
<div class="card-image-container">
<img src="../../../static/charizard.png"/>
</div>
</div>
<div class="card-content has-text-centered">
<div class="main">
<div class="title has-text-white">Charizard</div>
<div class="hp">hp 78</div>
</div>
<div class="stats columns is-mobile">
<div class="column">🔥<br>
<span class="tag is-warning">Type</span>
</div>
<div class="column center-column">199 lbs<br>
<span class="tag is-warning">Weight</span>
</div>
<div class="column">1.7 m <br>
<span class="tag is-warning">Height</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "CharizardCard",
};
</script>
<template>
<div class="container">
<div class="pokemon">
<PokemonCard />
</div>
</div>
</template>
<script>
import CharizardCard from "./components/CharizardCard";
export default {
name: "App",
components: {
PokemonCard: CharizardCard
}
};
</script>
<style lang="css" src="../styles.css">
// Styles from stylesheet
</style>
CharizardCard.vue
App.vue
Routing - Custom Client-Side Router
src/
components/
BlastoiseCard.vue
CharizardCard.vue
VenusaurCard.vue
App.vue
main.js
Routing - Custom Client-Side Router



CharizardCard.vue
VenusaurCard.vue
BlastoiseCard.vue
App.vue
Routing - Custom Client-Side Router
src/
router/
RouterLink.vue
RouterView.vue
routes.js
App.vue
main.js
components/
Routing - Custom Client-Side Router
routes.js
Responsible in mapping components to respective URL pathnames.
Routing - Custom Client-Side Router
import BlastoiseCard from "../components/BlastoiseCard";
import CharizardCard from "../components/CharizardCard";
import VenusaurCard from "../components/VenusaurCard";
export default [
{ path: "/", component: CharizardCard },
{ path: "/charizard", component: CharizardCard },
{ path: "/blastoise", component: BlastoiseCard },
{ path: "/venusaur", component: VenusaurCard }
];
src/router/routes.js
Routing - Custom Client-Side Router
RouterView.vue
Component responsible in rendering another component based on the app's location
Routing - Custom Client-Side Router
RouterView.vue
<template>
<div class="pokemon">
<component :is="currentView"></component>
</div>
</template>
<script>
import routes from './routes';
export default {
name: "RouterView",
data() {
return {
currentView: {}
}
},
created() {
if (!this.getRouteObject()) {
this.currentView = {
template: `
<h3 class="subtitle has-text-white">
Sorry, we couldn't find that Pokémon :(.
</h3>
`
};
} else {
this.currentView = this.getRouteObject().component;
}
},
methods: {
getRouteObject() {
return routes.find(
route => route.path === window.location.pathname
);
}
}
};
</script>
Routing - Custom Client-Side Router
<template>
<div class="container">
<div class="pokemon">
<router-view></router-view>
</div>
</div>
</template>
<script>
import RouterView from "./router/RouterView";
export default {
name: "App",
components: {
'router-view': RouterView
}
};
</script>
<style lang="css" src="../styles.css">
// Styles from stylesheet
</style>
App.vue
Routing - Custom Client-Side Router




/charizard
/blastoise
/venusaur
/pikachu
Routing - Custom Client-Side Router

<a href="?"></a>
Routing - Custom Client-Side Router
RouterLink.vue
Component that allows a change in browser location without making a web request
Routing - Custom Client-Side Router
<template>
<a @click="navigate" :href="to">{{ to }}</a>
</template>
<script>
export default {
name: "RouterLink",
props: {
to: {
type: String,
required: true
}
},
methods: {
navigate(evt) {
evt.preventDefault();
window.history.pushState(null, null, this.to);
}
}
};
</script>
RouterLink.vue
Routing - Custom Client-Side Router


Routing - Custom Client-Side Router
RouterLink
RouterView
created()
Event Bus
Routing - Custom Client-Side Router
import Vue from "vue";
export default new Vue();
event-bus.js
Routing - Custom Client-Side Router
<template>
<a @click="navigate" :href="to">{{ to }}</a>
</template>
<script>
import EventBus from './event-bus';
export default {
name: "RouterLink",
props: {
to: {
type: String,
required: true
}
},
methods: {
navigate(evt) {
evt.preventDefault();
window.history.pushState(null, null, this.to);
EventBus.$emit('navigate');
}
}
};
</script>
RouterLink.vue
Routing - Custom Client-Side Router
RouterView.vue
<template>...</template>
<script>
import routes from './routes';
import EventBus from '../event-bus';
export default {
name: "RouterView",
data() {
return {
currentView: {}
}
},
created() {
// Get correct component upon page load
...
// Get correct component upon redirect
EventBus.$on('navigate', () => {
this.currentView = this.getRouteObject().component;
});
},
methods: {
getRouteObject() {
return routes.find(
route => route.path === window.location.pathname
);
}
},
};
</script>
Routing - Custom Client-Side Router
<template>
<div class="container">
<div class="pokemon">
<router-view></router-view>
<div class="pokemon-links has-text-centered">
<router-link to="/charizard"></router-link>
<router-link to="/blastoise"></router-link>
<router-link to="/venusaur"></router-link>
</div>
</div>
</div>
</template>
<script>
import RouterView from "./router/RouterView";
import RouterLink from "./router/RouterLink";
export default {
name: "App",
components: {
'router-view': RouterView,
'router-link': RouterLink
}
};
</script>
<style lang="css" src="../styles.css">
// Styles from stylesheet
</style>
App.vue
Routing - Custom Client-Side Router
popstate
Event to handle browser navigation events
Routing - Custom Client-Side Router
<template>
...
</template>
<script>
import RouterView from "./router/RouterView";
import RouterLink from "./router/RouterLink";
import EventBus from './event-bus';
export default {
name: "App",
created() {
window.addEventListener('popstate', () => {
EventBus.$emit('navigate');
});
},
components: {
'router-view': RouterView,
'router-link': RouterLink
}
};
</script>
<style lang="css" src="../styles.css">
// Styles from stylesheet
</style>
App.vue
Routing - Custom Client-Side Router
- routes
Responsible in mapping components to respective url pathnames.
- router-view
- router-link
Component responsible in rendering another specified component based on the app’s location.
Component that allows the user to change the location of the browser without making a web request
Same pieces that Vue Router provides!
❗
Routing - Vue Router
Vue Router is the official router of Vue. It deeply integrates with Vue to make building Single Page applications a breeze.
Routing - Vue Router
npm install vue-router --save
Routing - Vue Router
Vue.use(VueRouter);
import Vue from 'vue';
import VueRouter from 'vue-router';
Routing - Vue Router
src/
router/
RouterLink.vue
RouterView.vue
routes.js
App.vue
main.js
components/
router.js
Routing - Vue Router
import Vue from 'vue';
import VueRouter from 'vue-router';
import BlastoiseCard from './components/BlastoiseCard';
import CharizardCard from './components/CharizardCard';
import VenusaurCard from './components/VenusaurCard';
Vue.use(VueRouter);
const routes = [
{path: '/', component: CharizardCard},
{path: '/charizard', component: CharizardCard},
{path: '/blastoise', component: BlastoiseCard},
{path: '/venusaur', component: VenusaurCard},
{
path: '*',
component: {
template: `
<h3 class="subtitle has-text-white">
Sorry. We couldn't find that Pokémon :(.
</h3>
`
}
}
];
export default new VueRouter({
mode: 'history',
routes
});
router.js
Routing - Vue Router
https://localhost:8080/#/blastoise
Hash mode URLs
Never sent to the server
Routing - Vue Router
https://localhost:8080/blastoise
History mode URLs
Routing - Vue Router
import Vue from "vue";
import App from "./App";
import router from "./router";
new Vue({
el: "#app",
router,
render: h => h(App)
});
main.js
Routing - Vue Router
<template>
<div class="container">
<div class="pokemon">
<router-view></router-view>
<div class="pokemon-links has-text-centered">
<router-link to="/charizard">/charizard</router-link>
<router-link to="/blastoise">/blastoise</router-link>
<router-link to="/venusaur">/venusaur</router-link>
</div>
</div>
</div>
</template>
<script>
export default {
name: "App"
};
</script>
<style lang="css" src="../styles.css">
// Styles from stylesheet
</style>
App.vue
Routing - Vue Router




/charizard
/blastoise
/venusaur
/pikachu
Routing - Vue Router
Vue Router > simple custom router
- Well Tested
- Consistency between different browsers
- Dynamic route matching
- Nested routes
- Navigation guards
...
Routing - Vue Router - Dynamic Route Matching
Dynamic Route Matching involves mapping routes with the same pattern to a single component.
Routing - Vue Router - Dynamic Route Matching
src/
components/
BlastoiseCard.vue
CharizardCard.vue
EeveeCard.vue
GolbatCard.vue
MachampCard.vue
MoltresCard.vue
OnixCard.vue
VenusaurCard.vue
WigglytuffCard.vue
...Card.vue
AlakazamCard.vue
src/
components/
PokemonCard.vue
Routing - Vue Router - Dynamic Route Matching
const routes = [
{path: '/', component: CharizardCard},
...
];
{path: '/pokemon/:id', component: PokemonCard}
{path: '/charizard', component: CharizardCard},
{path: '/blastoise', component: BlastoiseCard},
{path: '/venusaur', component: VenusaurCard},
Routing - Vue Router - Dynamic Route Matching
<template>
...
</template>
<script>
export default {
name: "PokemonCard",
created() {
console.log(this.$route.params.id);
}
};
</script>
PokemonCard.vue
/pokemon/charizard
charizard
Routing - Vue Router - Dynamic Route Matching
data: [
{
id: 'charizard',
hp: 78,
type: 🔥,
weight_lbs: 199,
height_m: 1.7,
},
...
]
Routing - Vue Router - Dynamic Route Matching
export default [
{
id: "charizard",
name: "Charizard",
hp: 78,
type: "🔥",
weight_lbs: 199,
height_m: 1.7
},
{
id: "blastoise",
name: "Blastoise",
hp: 79,
type: "💧",
weight_lbs: 223,
height_m: 1.6
},
{
id: "venusaur",
name: "Venusaur",
hp: 80,
type: "🍃",
weight_lbs: 220,
height_m: 2.0
}
];
data.js
Routing - Vue Router - Dynamic Route Matching
import Vue from "vue";
import VueRouter from "vue-router";
import PokemonCard from "./components/PokemonCard";
Vue.use(VueRouter);
const routes = [
{
path: "/",
component: {
template: `
<h3 class="subtitle has-text-white">
Select a Pokémon from the links below!
</h3>
`
}
},
{
path: "/pokemon/:id",
component: PokemonCard
},
{
path: "*",
component: {
template: `
<h3 class="subtitle has-text-white">
Sorry. We couldn't find that Pokémon :(.
</h3>
`
}
}
];
export default new VueRouter({
mode: "history",
routes
});
router.js
Routing - Vue Router - Dynamic Route Matching
<template>
<div class="card has-text-weight-bold has-text-white"
:class="'card--' + pokemon.id">
<div class="card-image">
<div class="card-image-container">
<img :src="getPokemonImg"/>
</div>
</div>
<div class="card-content has-text-centered">
<div class="main">
<div class="title has-text-white">{{pokemon.name}}</div>
<div class="hp hp-venusaur">hp {{pokemon.hp}}</div>
</div>
<div class="stats columns is-mobile">
<div class="column">{{pokemon.type}}<br>
<span class="tag is-danger">Type</span>
</div>
<div class="column center-column">{{pokemon.weight_lbs}} lbs<br>
<span class="tag is-danger">Weight</span>
</div>
<div class="column">{{pokemon.height_m}} m<br>
<span class="tag is-danger">Height</span>
</div>
</div>
</div>
</div>
</template>
<script>
import pokemonData from '../data';
export default {
name: "PokemonCard",
data() {
return {
pokemon: {
id: '',
name: '',
hp: 0,
type: '',
weight_lbs: 0,
height_m: 0,
},
}
},
created() {
this.getPokemon();
},
computed: {
getPokemonImg() {
// eslint-disable-next-line no-undef
return require(`../../static/${this.pokemon.id}.png`);
}
},
watch: {
$route() {
this.getPokemon();
}
},
methods: {
getPokemon() {
const idParam = this.$route.params.id;
const pokemonFromData = pokemonData.find(pokemon => pokemon.id === idParam);
if (pokemonFromData) {
this.pokemon = pokemonFromData;
}
}
}
};
</script>
PokemonCard.vue
Routing - Vue Router - Dynamic Route Matching
Dynamic Route Matching with props is more ideal!
Routing - Vue Router - Dynamic Route Matching
const routes = [
...,
... ];
{
path: '/pokemon/:id',
component: PokemonCard,
props: true
}
Routing - Vue Router - Dynamic Route Matching
<template>
...
</template>
<script>
export default {
name: "PokemonCard",
props: ['id'],
created() {
console.log(this.id);
}
};
</script>
PokemonCard.vue
/pokemon/charizard
charizard
How do we handle dynamic routes with unavailable params?
e.g.
https://localhost:8080/pokemon/pikachu
not available :(
<template>
<div>
<div v-if="pokemon.id">
<!-- Display Pokemon Details -->
</div>
<div v-if="!pokemon.id">
<h3 class="subtitle has-text-white">
Sorry. We couldn't find that Pokémon :(.
</h3>
</div>
</div>
</template>
<script>
// ...
</script>
PokemonCard.vue
Nested Routes allows us to compose components multiple levels deep that corresponds to segments of a URL path.
Routing - Vue Router - Nested Routes
Routing - Vue Router - Nested Routes

App
PokemonCard
PokemonDetails
PokemonStats
Routing - Vue Router - Nested Routes
/pokemon/:id/details
/pokemon/:id/stats
PokemonCard
PokemonDetails
PokemonCard
PokemonStats
Routing - Vue Router - Nested Routes
const routes = [
...,
];
{ path: '/pokemon/:id', component: PokemonCard, props: true, children: [ { path: 'details', component: PokemonDetails, props: true }, { path: 'stats', component: PokemonStats, props: true } ] }
router.js
Routing - Vue Router - Nested Routes
<template>
<div class="pokemon">
<div class="card-image">
...
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: "PokemonCard",
...
};
</script>
PokemonCard.vue
Responsible in rendering sub-component for nested URL route.
Routing - Vue Router - Nested Routes
src/
components/
PokemonCard.vue
PokemonDetails.vue
PokemonStats.vue
App.vue
main.js
Routing - Vue Router - Nested Routes
<template>
<div class="card-content has-text-centered">
<div class="main">
<div class="title has-text-white">{{pokemon.name}}</div>
<div class="hp hp-venusaur">hp {{pokemon.hp}}</div>
</div>
</div>
</template>
<script>
import pokemonData from '../data';
export default {
name: "PokemonDetails",
props: ['id'],
data() {
return {
pokemon: {
name: '',
hp: 0
},
}
},
created() {
this.getPokemon();
},
watch: {
id() {
this.getPokemon();
}
},
methods: {
getPokemon() {
const idParam = this.id;
const pokemonFromData = pokemonData.find(pokemon => pokemon.id === idParam);
if (pokemonFromData) {
this.pokemon.name = pokemonFromData.name;
this.pokemon.hp = pokemonFromData.hp;
}
}
}
};
</script>
PokemonDetails.vue
Routing - Vue Router - Nested Routes
<template>
<div class="card-content has-text-centered">
<div class="stats columns is-mobile">
<div class="column">{{pokemon.type}}<br>
<span class="tag is-danger">Type</span>
</div>
<div class="column center-column">{{pokemon.weight_lbs}} lbs<br>
<span class="tag is-danger">Weight</span>
</div>
<div class="column">{{pokemon.height_m}} m<br>
<span class="tag is-danger">Height</span>
</div>
</div>
</div>
</template>
<script>
import pokemonData from '../data';
export default {
name: "PokemonStats",
props: ['id'],
data() {
return {
pokemon: {
type: '',
weight_lbs: 0,
height_m: 0
},
}
},
created() {
this.getPokemon();
},
watch: {
id() {
this.getPokemon();
}
},
methods: {
getPokemon() {
const idParam = this.id;
const pokemonFromData = pokemonData.find(pokemon => pokemon.id === idParam);
if (pokemonFromData) {
this.pokemon.type = pokemonFromData.type;
this.pokemon.weight_lbs = pokemonFromData.weight_lbs;
this.pokemon.height_m = pokemonFromData.height_m;
}
}
}
};
</script>
PokemonStats.vue
Routing - Vue Router - Nested Routes
import Vue from "vue";
import VueRouter from "vue-router";
import PokemonCard from "./components/PokemonCard";
import PokemonDetails from "./components/PokemonDetails";
import PokemonStats from "./components/PokemonStats";
Vue.use(VueRouter);
const routes = [
...,
{
path: "/pokemon/:id",
component: PokemonCard,
props: true,
children: [
{
path: "details",
component: PokemonDetails,
props: true
},
{
path: "stats",
component: PokemonStats,
props: true
}
]
},
...
];
export default new VueRouter({
mode: "history",
routes
});
router.js
Routing - Vue Router - Nested Routes
<template>
<div class="card has-text-weight-bold has-text-white"
:class="'card--' + id">
<div class="card-image">
<div class="card-image-container">
<img :src="getPokemonImg"/>
</div>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "PokemonCard",
props: ['id'],
computed: {
getPokemonImg() {
// eslint-disable-next-line no-undef
return require(`../../static/${this.id}.png`);
}
}
};
</script>
PokemonCard.vue
Navigation Guards give us the ability to guard navigations by either redirecting it or cancelling it.
Routing - Vue Router - Navigation Guards
Routing - Vue Router - Navigation Guards
Three types of Navigation Guards
1. Global - for all navigation routes
2. Per-route - for a certain route
3. In-component - for certain components
Routing - Vue Router - Navigation Guards
Global Navigation Guards
const router = new VueRouter({ ... });
router.beforeEach((to, from, next) => {
...
});
to.path
next();
next('/about');
next(false);
Routing - Vue Router - Navigation Guards
Global After Hooks
const router = new VueRouter({ ... });
router.afterEach((to, from) => {
...
});
After hooks are not navigation guards since the route is already complete at that moment!
Routing - Vue Router - Navigation Guards
const routes = [ ..., ];
{ path: '/pokemon/:id', component: PokemonCard, props: true, beforeEnter: (to, from, next) => { ... } }
Per-Route Navigation Guards
Routing - Vue Router - Navigation Guards
In-Component Navigation Guards
<template> ... </template> <script> export default { name: "PokemonCard", beforeRouteEnter(to, from, next) {}, beforeRouteUpdate(to, from, next) {}, beforeRouteLeave(to, from, next) {}, }; </script>
Routing - Vue Router - Navigation Guards
import ...;
Vue.use(VueRouter);
const routes = [
...,
{
path: "*",
component: {
template: `
<h3 class="subtitle has-text-white">
Sorry. We couldn't find that Pokémon :(.
</h3>
`
},
beforeEnter: (to, from, next) => {
console.log("Are you looking for a Pokemon? Use the links below!");
next();
}
}
];
export default new VueRouter({
mode: "history",
routes
});
router.js
Routing - Vue Router - Navigation Guards
<template>
...
</template>
<script>
import pokemonData from '../data';
export default {
name: "PokemonCard",
...,
beforeRouteUpdate(to, from, next) {
if (to.path === '/pokemon/venusaur') next('/pokemon/charizard');
else next();
},
...
};
</script>
PokemonCard.vue
Routing - Vue Router
Vue Router has more functionality:
Routing - Vue Router
- Vue Router Documentation
- Let's build a Custom Vue Router
- Understanding client side routing with Vue.js: JSCamp18
Routing - Chapter Summary!
-
Routing refers to splitting an applications UI based on rules derived from the browser URL.
-
Client-side applications are often labelled as Single-Page applications since the server provides a single template.
-
Vue Router is Vue's official client side routing library.
-
routes: Array that maps components to respective URL pathnames.
-
<router-view>: Component responsible in rendering another specified component based on the app's location.
-
<router-link>: Component that allows the user to change the location of the browser without making a web request.
-
Vue Router features we've seen
-
Dynamic Route Matching: mapping routes with the same pattern to a single component.
-
Nested Routes: compose components multiple levels deep that corresponds to segments of a URL path.
-
Navigation Guards: guard navigations by either redirecting it or cancelling it.
Routing - Exercise 💪!
Routing - Exercise 💪!
App
CityComponent

Routing - Exercise 💪!
Starting Point
Routing - Exercise 💪!
Vue Router
Creates routes to each different city with paths like ''/city/los-angeles".
Solution 👀
Create a router instance (e.g. router.js) that exports a router instance and contains a routes array.
Vue Router/Dynamic Routes
Creates a single dynamic route for the different cities: "/city/:id".
Solution 👀
Create a router instance (e.g. router.js) that exports a router instance and contains a routes array.
Create simple routes for homepage/not-found screens.
Create simple routes for homepage/not-found screens.
🎉
A Full Day of Vue.js Workshop - 05 Routing
By djirdehh
A Full Day of Vue.js Workshop - 05 Routing
- 429