Vue.js course Β· Module 10: Vue Router

Nested Routes

2 min read
In this lesson5

The NOVA LAB base has modules, modules have systems, systems have components - it's a hierarchy. Similarly, we can nest routes, creating complex layouts with subpages.

Nested Routes Configuration

The key is the children property in the route definition:

1const routes = [
2  {
3    path: '/mission-control',
4    name: 'mission-control',
5    component: MissionControlLayout,
6    children: [
7      {
8        path: '',  // default view for /mission-control
9        name: 'mission-overview',
10        component: MissionOverview
11      },
12      {
13        path: 'systems',  // /mission-control/systems
14        name: 'mission-systems',
15        component: MissionSystems
16      },
17      {
18        path: 'crew',  // /mission-control/crew
19        name: 'mission-crew',
20        component: MissionCrew
21      }
22    ]
23  }
24]

Important: children paths WITHOUT a leading slash are relative to the parent. So 'systems' becomes '/mission-control/systems'.

Parent Component with RouterView

The parent component MUST contain <RouterView> - that's where children render:

1<!-- MissionControlLayout.vue -->
2<template>
3  <div class="layout">
4    <aside class="sidebar">
5      <h2>Control Center</h2>
6      <nav>
7        <RouterLink :to="{ name: 'mission-overview' }">
8          Overview
9        </RouterLink>
10        <RouterLink :to="{ name: 'mission-systems' }">
11          Systems
12        </RouterLink>
13        <RouterLink :to="{ name: 'mission-crew' }">
14          Crew
15        </RouterLink>
16      </nav>
17    </aside>
18
19    <main class="content">
20      <!-- Nested routes render here -->
21      <RouterView />
22    </main>
23  </div>
24</template>

Default Child Route

An empty path: '' defines the default view displayed when the user navigates to the parent path:

1children: [
2  {
3    path: '',  // /mission-control
4    component: MissionOverview
5  },
6  {
7    path: 'systems',  // /mission-control/systems
8    component: MissionSystems
9  }
10]

Redirect

You can also use a redirect instead of a default view:

1children: [
2  {
3    path: '',
4    redirect: '/mission-control/dashboard'
5  },
6  {
7    path: 'dashboard',
8    component: Dashboard
9  }
10]

Named Views

A single route can render multiple components in different <RouterView> slots:

1const routes = [
2  {
3    path: '/mission-control',
4    components: {
5      default: MissionMain,
6      sidebar: MissionSidebar,
7      footer: MissionFooter
8    }
9  }
10]
1<template>
2  <RouterView name="sidebar" />
3  <RouterView /> <!-- default -->
4  <RouterView name="footer" />
5</template>
Code for this lesson: App.vue
1<script setup>
2import { ref } from 'vue'
3
4// Simulation of navigation guards
5const isAuthenticated = ref(false)
6const currentRoute = ref('home')
7const guardLog = ref([])
8const pageTitle = ref('NOVA LAB - Home')
9
10const routes = [
11  { name: 'home', label: 'Home', meta: {} },
12  { name: 'login', label: 'Login', meta: { requiresGuest: true } },
13  { name: 'dashboard', label: 'Dashboard', meta: { requiresAuth: true } },
14  { name: 'admin', label: 'Admin', meta: { requiresAuth: true, requiresAdmin: true } }
15]
16
17function navigate(routeName) {
18  const route = routes.find(r => r.name === routeName)
19  if (!route) return
20
21  guardLog.value = []
22
23  // Simulation of beforeEach
24  guardLog.value.push('beforeEach: ' + currentRoute.value + ' -> ' + routeName)
25
26  // Check requiresAuth
27  if (route.meta.requiresAuth && !isAuthenticated.value) {
28    guardLog.value.push('BLOCKED: requiresAuth - redirect to login')
29    currentRoute.value = 'login'
30    pageTitle.value = 'NOVA LAB - Login'
31    return
32  }
33
34  // Check requiresGuest
35  if (route.meta.requiresGuest && isAuthenticated.value) {
36    guardLog.value.push('BLOCKED: requiresGuest - redirect to home')
37    currentRoute.value = 'home'
38    pageTitle.value = 'NOVA LAB - Home'
39    return
40  }
41
42  // Navigation allowed
43  guardLog.value.push('ALLOWED: navigation ok')
44  currentRoute.value = routeName
45  pageTitle.value = 'NOVA LAB - ' + route.label
46
47  // Simulation of afterEach
48  guardLog.value.push('afterEach: title = ' + pageTitle.value)
49}
50
51function toggleAuth() {
52  isAuthenticated.value = !isAuthenticated.value
53  guardLog.value.push('Auth: ' + (isAuthenticated.value ? 'logged in' : 'logged out'))
54}
55</script>
56
57<template>
58  <div class="nova-lab">
59    <h1>NOVA LAB - Navigation Guards</h1>
60
61    <div class="auth-bar">
62      <span>Status: {{ isAuthenticated ? 'Logged in' : 'Logged out' }}</span>
63      <button @click="toggleAuth" class="auth-btn">
64        {{ isAuthenticated ? 'Log out' : 'Log in' }}
65      </button>
66    </div>
67
68    <nav class="main-nav">
69      <button
70        v-for="route in routes"
71        :key="route.name"
72        @click="navigate(route.name)"
73        :class="{ active: currentRoute === route.name }"
74        class="nav-btn"
75      >
76        {{ route.label }}
77        <span v-if="route.meta.requiresAuth" class="badge lock">LOCK</span>
78        <span v-if="route.meta.requiresGuest" class="badge guest">GUEST</span>
79      </button>
80    </nav>
81
82    <div class="guard-log">
83      <h3>Guard Log:</h3>
84      <div v-for="(log, i) in guardLog" :key="i" class="log-entry"
85        :class="{
86          blocked: log.includes('BLOCKED'),
87          allowed: log.includes('ALLOWED')
88        }"
89      >
90        {{ log }}
91      </div>
92    </div>
93
94    <div class="page-content">
95      <h2>{{ pageTitle }}</h2>
96      <pre>
97// Guards configuration:
98router.beforeEach((to, from) => {
99  if (to.meta.requiresAuth && !isAuthenticated) {
100    return { name: 'login' }
101  }
102})
103
104router.afterEach((to) => {
105  document.title = to.meta.title || 'NOVA LAB'
106})</pre>
107    </div>
108  </div>
109</template>
110
111<style scoped>
112.nova-lab {
113  background: #0a0e27;
114  color: #00ff88;
115  min-height: 100vh;
116  padding: 20px;
117  font-family: monospace;
118}
119h1 { color: #00ffff; margin-bottom: 10px; }
120h2 { color: #00b4d8; }
121h3 { color: #00ffff; margin-bottom: 5px; }
122.auth-bar {
123  display: flex;
124  align-items: center;
125  gap: 10px;
126  padding: 8px 12px;
127  background: #1a1e37;
128  margin-bottom: 10px;
129  color: #c0c0c0;
130}
131.auth-btn {
132  background: rgba(0, 255, 136, 0.2);
133  border: 1px solid #00ff88;
134  color: #00ff88;
135  padding: 4px 10px;
136  cursor: pointer;
137  font-family: monospace;
138}
139.main-nav { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
140.nav-btn {
141  background: rgba(0, 180, 216, 0.1);
142  border: 1px solid #00b4d8;
143  color: #00ff88;
144  padding: 6px 12px;
145  cursor: pointer;
146  font-family: monospace;
147  position: relative;
148}
149.nav-btn.active {
150  background: #00b4d8;
151  color: #0a0e27;
152}
153.badge {
154  font-size: 9px;
155  padding: 1px 4px;
156  margin-left: 4px;
157}
158.badge.lock { color: #ff4444; }
159.badge.guest { color: #ffaa00; }
160.guard-log {
161  background: #1a1e37;
162  padding: 10px;
163  margin-bottom: 10px;
164  max-height: 120px;
165  overflow-y: auto;
166}
167.log-entry {
168  font-size: 12px;
169  color: #888;
170  padding: 2px 0;
171}
172.log-entry.blocked { color: #ff4444; }
173.log-entry.allowed { color: #00ff88; }
174.page-content {
175  background: rgba(0, 180, 216, 0.05);
176  border: 1px solid #1a1e37;
177  padding: 15px;
178}
179pre {
180  background: #1a1e37;
181  padding: 10px;
182  color: #00ff88;
183  font-size: 11px;
184  margin-top: 10px;
185}
186</style>

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How do you define nested routes in Vue Router?

  2. 2. Where are nested route components rendered?

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Code editor

    Configure a parent route with a children array and a layout with <RouterView>.

  • Vertical ordering

    Arrange the elements from outermost to innermost:

  • Click in order

    Arrange the nested route definition code:

  • Code editor

    Add a default child route with an empty path and an overview component.

Useful articles