Vue.js course Β· Module 10: Vue Router
Nested Routes
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. How do you define nested routes in Vue Router?
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.