Tutorial

How To Use Nested Routes in Vue.js

Vue.js

Introduction

As your Vue.js Single Page Applications (SPAs) become moderately complex, you start to need Vue Router, and, moreover, nested routes. Nested routes allow for more complex user interfaces with components nested inside each other.

In this article, you will build out an example Vue.js project that highlights the utility of nested routes.

Prerequisites

To complete this tutorial, you will need:

This tutorial was verified with Node v16.5.0, npm v7.20.0, vue v2.6.14, and vue-router v3.5.2.

Step 1 — Setting Up the Project

To quickly set up the project, this article will recommend using @vue/cli.

Note: This article will take the approach of using npx to avoid a global installation of @vue/cli;

  • npx @vue/cli create vue-nested-routes-example

When prompted with configurations options, you may wish to Manually select features and include Router.

Then, navigate to the newly created project directory:

  • cd vue-nested-routes-example

If at this point you did not install Vue Router at creation, you can add it to your project now.

Step 2 — Building the Application

The following CSS will help us with positioning elements for our user interface.

Under the 'public directory, create a grid.css file:

public/grid.css
.row1 {
  grid-row-start: 1;
  grid-row-end: 2;
}

.row12 {
  grid-row-start: 1;
  grid-row-end: 3;
}

.row123 {
  grid-row-start: 1;
  grid-row-end: 4;
}

.row2 {
  grid-row-start: 2;
  grid-row-end: 3;
}

.row23 {
  grid-row-start: 2;
  grid-row-end: 4;
}

.row3 {
  grid-row-start: 3;
  grid-row-end: 4;
}

.col1 {
  grid-column-start: 1;
  grid-column-end: 2;
}

.col12 {
  grid-column-start: 1;
  grid-column-end: 3;
}

.col123 {
  grid-column-start: 1;
  grid-column-end: 4;
}

.col1234 {
  grid-column-start: 1;
  grid-column-end: 5;
}

.col2 {
  grid-column-start: 2;
  grid-column-end: 3;
}

.col23 {
  grid-column-start: 2;
  grid-column-end: 4;
}

.col234 {
  grid-column-start: 2;
  grid-column-end: 5;
}

.col3 {
  grid-column-start: 3;
  grid-column-end: 4;
}

.col34 {
  grid-column-start: 3;
  grid-column-end: 5;
}

.col4 {
  grid-column-start: 4;
  grid-column-end: 5;
}

We’ll be making use of CSS grid.

Add grid.css to index.html:

public/index.html
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <link rel="stylesheet" href="grid.css">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

Next, let’s make some changes to the default files that @vue/cli created.

Open App.vue in your code editor. And make the following modifications to the HTML markup in App.vue:

src/App.vue
<template>
  <div id="app">
    <h1 class="row1 col12">Example</h1>
    <a class="row1 col3">About</a>
    <a class="row1 col4">Nested Pages</a>
    <div class="row2 col234"></div>
  </div>
</template>

And CSS styling in App.vue:

src/App.vue
html, body {
  height: 100vh;
  width: 100vw;
  padding: 0;
  margin: 0;
}

#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  padding: 2%;
  height: 100%;
  display: grid;
  grid-template-rows: 20% 80%;
  grid-template-columns: 25% 25% 25% 25%;
}

Save the changes to your files.

Then run the following command in your terminal:

  • npm run serve

Visit localhost:8080 in your web browser and observe the grid layout.

Now we can begin routing.

Step 3 — Applying Vue Routing

@vue/cli will build a main.js file:

src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

And a router/index.js file:

src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

And Home.vue and About.vue files in the views directory.

Let’s revisit App.vue and change the anchor tag for About to a <router-link> tag with an attribute of to="/about". Then, change the second div to a <router-view> tag. Make sure to keep the grid positioning class attributes intact.

src/App.vue
<template>
  <div id="app">
    <h1 class="row1 col12">Example</h1>
    <router-link to="/about" class="row1 col3">About</router-link>
    <a class="row1 col4">Nested Pages</a>
    <router-view class="row2 col234" />
  </div>
</template>

We now have a functional site skeleton with routing handled for the About page.

Step 4 — Building a Nested View

We’re focusing on the routing functionality here so we’re not going to get fancy with styling. Even so, the Nested page is going to look more elaborate.

To start, create a NestedPages.vue file in the views directory:

src/views/NestedPages.vue
<template>
  <div id="nested">
    <h2 class="row1">Nested Pages</h2>
  </div>
</template>

<script>
  export default {
    name: 'NestedPages'
  }
</script>

<style scoped>
div {
  text-align: center;
}
</style>

And reference it in router/index.js:

src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import NestedPages from '../views/NestedPages.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/nested',
    component: NestedPages
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

Also, create the following two components which will ultimately be nested in NestedPages.vue.

Create a NestedPageOne.vue file:

src/components/NestedPageOne.vue
<template>
  <div>
    <p>Nested Page One</p>
  </div>
</template>

<script>
  export default {
    name: 'NestedPageOne'
  }
</script>

<style scoped>
</style>

And create a similiar NestedPageTwo.vue file:

src/components/NestedPageTwo.vue
<template>
  <div>
    <p>Nested Page Two</p>
  </div>
</template>

<script>
  export default {
    name: 'NestedPageTwo'
  }
</script>

<style scoped>
</style>

Finally, revisit App.vue and update the Nested Pages link to use <router-link>:

src/App.vue
<template>
  <div id="app">
    <h1 class="row1 col12">Example</h1>
    <router-link to="/about" class="row1 col3">About</router-link>
    <router-link to="/nested" class="row1 col4">Nested Pages</router-link>
    <router-view class="row2 col234" />
  </div>
</template>

At this point, you have a new nested route and two new components.

Step 5 — Configuring Nested Children Routes

Revisit NestedPages.vue and add <router-link> for Nested Page One and Nested Page Two.

src/views/NestedPages.vue
<template>
  <div id="nested">
    <h2 class="row1">Nested Pages</h2>
    <div class="flex-container row2">
      <router-link to="/nested/one">Nested Page One</router-link>
      <router-link to="/nested/two">Nested Page Two</router-link>
    </div>
    <router-view class="row3" />
  </div>
</template>

<script>
  export default {
    name: 'NestedPages'
  }
</script>

<style scoped>
div {
  text-align: center;
}

#nested {
  display: grid;
  grid-template-rows: 20% 40% 40%;
}

.flex-container {
  display: flex;
  justify-content: space-around;
}
</style>

Now, let’s update router/index.js to reference those nested routes using children.

router/index.js
// ...

import NestedPageOne from '../components/NestedPageOne.vue'
import NestedPageTwo from '../components/NestedPageTwo.vue'

// ...

const routes = [
  // ...
  {
    path: '/nested',
    component: NestedPages,
    children: [
      <^>{
        path: '/nested/one',
        component: NestedPageOne
      },
      {
        path: '/nested/two',
        component: NestedPageTwo
      }
    ]
  },
  // ...
];

// ...

Note: Note that the nesting of children can continue infinitely.

Save the changes to your files.

Then run the following command in your terminal:

  • npm run serve

Revisit localhost:8080 in your web browser. When you visit the /nested route, you can visit the NestedPageOne and NestedPageTwo children routes.

Conclusion

In this article, you built out an example Vue.js project that highlights the utility of nested routes.

Other things to keep in mind on the topic — we could have routes defined with dynamic segments such as path: '/location/:id'. Then, on the views for those routes, we can reference that id as this.$route.params. This is useful when you have more data of a particular type (users, pictures, etc.) that you are wishing to display on your site or app.

Creative Commons License