Skip to main content
Vue.js for Beginners to Advanced
CHAPTER 15 Beginner

Dynamic Routing and Navigation Guards

Updated: May 18, 2026
5 min read

# CHAPTER 15

Dynamic Routing and Navigation Guards

1. Chapter Introduction

Real applications need dynamic routes (/user/123) and protected routes (auth required). Navigation guards are Vue Router's middleware system — they run before each navigation and can redirect, cancel, or validate route access.

2. Learning Objectives

  • Create dynamic routes with params.
  • Use nested (child) routes.
  • Implement beforeEach navigation guards.
  • Protect routes with authentication guards.
  • Pass data to routes via meta fields.

3. Dynamic Route Parameters

javascript
1234567891011121314151617
// router/index.js
const routes = [
  // Static: /users
  { path: '/users', component: UserListView },

  // Dynamic: /users/123, /users/alice
  { path: '/users/:id', name: 'user', component: UserDetailView },

  // Multiple params: /posts/vue-js/chapter-14
  { path: '/posts/:category/:slug', name: 'post', component: PostView },

  // Optional param: /search OR /search/vue
  { path: '/search/:query?', name: 'search', component: SearchView },

  // Regex param: /products/123 (only numbers)
  { path: '/products/:id(\\d+)', component: ProductView },
]
vue
12345678910111213141516171819202122232425262728293031
<!-- UserDetailView.vue -->
<script setup>
import { useRoute, onBeforeRouteUpdate } from &#039;vue-router'
import { ref, watch, onMounted } from &#039;vue'

const route = useRoute()
const user = ref(null)

async function loadUser(id) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
  user.value = await res.json()
}

// Load on mount
onMounted(() => loadUser(route.params.id))

// Reload when params change (user navigates /users/1 → /users/2)
// When component is REUSED, onMounted doesn't fire again!
watch(() => route.params.id, (newId) => loadUser(newId))

// OR use the route guard (alternative):
onBeforeRouteUpdate((to) => loadUser(to.params.id))
</script>

<template>
  <div v-if="user">
    <h1>{{ user.name }}</h1>
    <p>{{ user.email }}</p>
    <p>Route param: {{ route.params.id }}</p>
  </div>
</template>

4. Nested Routes

javascript
123456789101112131415161718192021222324
const routes = [
  {
    path: &#039;/dashboard&#039;,
    component: DashboardLayout,  // Layout component with <RouterView>
    children: [
      {
        path: &#039;&#039;,           // /dashboard — default child
        component: DashboardHome
      },
      {
        path: &#039;profile&#039;,   // /dashboard/profile
        component: DashboardProfile
      },
      {
        path: &#039;settings&#039;,  // /dashboard/settings
        component: DashboardSettings,
        children: [
          { path: &#039;account&#039;, component: AccountSettings },   // /dashboard/settings/account
          { path: &#039;security&#039;, component: SecuritySettings }  // /dashboard/settings/security
        ]
      }
    ]
  }
]
vue
1234567891011121314
<!-- DashboardLayout.vue -->
<template>
  <div class="dashboard-layout">
    <aside class="sidebar">
      <RouterLink to="/dashboard">Overview</RouterLink>
      <RouterLink to="/dashboard/profile">Profile</RouterLink>
      <RouterLink to="/dashboard/settings">Settings</RouterLink>
    </aside>
    <main class="content">
      <!-- Child routes render here -->
      <RouterView />
    </main>
  </div>
</template>

5. Navigation Guards

javascript
1234567891011121314151617181920212223
// Global beforeEach guard — runs before EVERY route change
router.beforeEach((to, from, next) => {
  const authStore = useAuthStore()

  // Check if route requires auth
  if (to.meta.requiresAuth && !authStore.isLoggedIn) {
    // Redirect to login with return URL
    next({ name: &#039;login&#039;, query: { redirect: to.fullPath } })
  } else if (to.meta.guestOnly && authStore.isLoggedIn) {
    // Redirect logged-in users away from login/register pages
    next({ name: &#039;dashboard&#039; })
  } else {
    next()  // Allow navigation
  }
})

// Global afterEach — runs after navigation completes
router.afterEach((to, from) => {
  // Update page title
  document.title = to.meta.title || &#039;MyApp&#039;
  // Analytics tracking
  trackPageView(to.fullPath)
})

6. Route Meta Fields

javascript
123456789101112131415161718192021222324
const routes = [
  {
    path: &#039;/&#039;,
    name: &#039;home&#039;,
    component: HomeView,
    meta: { title: &#039;Home — MyApp&#039; }
  },
  {
    path: &#039;/dashboard&#039;,
    name: &#039;dashboard&#039;,
    component: DashboardView,
    meta: {
      title: &#039;Dashboard&#039;,
      requiresAuth: true,   // Protected route
      roles: [&#039;admin&#039;, &#039;editor&#039;]  // Role-based access
    }
  },
  {
    path: &#039;/login&#039;,
    name: &#039;login&#039;,
    component: LoginView,
    meta: { guestOnly: true, title: &#039;Sign In&#039; }
  }
]

7. In-Component Guards

vue
12345678910111213141516171819
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from &#039;vue-router'
import { ref } from &#039;vue'

const hasUnsavedChanges = ref(false)

// Guard: before leaving THIS component
onBeforeRouteLeave((to, from) => {
  if (hasUnsavedChanges.value) {
    const confirm = window.confirm(&#039;You have unsaved changes. Leave anyway?')
    if (!confirm) return false  // Cancel navigation
  }
})

// Guard: before route params update (component reuse)
onBeforeRouteUpdate(async (to) => {
  await loadData(to.params.id)
})
</script>

8. Common Mistakes

  • Not watching route.params for re-renders: When navigating from /user/1 to /user/2, Vue REUSES the component — onMounted does NOT fire again. Always watch route.params.
  • Calling next() multiple times: In beforeEach, call next() exactly once per navigation.

9. MCQs

Question 1

Dynamic route segment is defined with?

Question 2

Nested routes render in?

Question 3

beforeEach guard receives?

Question 4

next() in a guard means?

Question 5

next({ name: 'login' }) in a guard?

Question 6

Route meta fields are accessed in guard via?

Question 7

onBeforeRouteLeave fires when?

Question 8

Reusing component on param change requires?

Question 9

Optional route param syntax?

Question 10

router.afterEach is good for?

10. Interview Questions

  • Q: How do you protect routes in Vue Router? Implement an auth guard.
  • Q: Why might onMounted not fire when navigating between dynamic routes?

11. Summary

Dynamic routing with :params enables data-driven URLs. Nested routes create layout-based navigation hierarchies. Navigation guards are Vue Router's middleware — beforeEach is the primary location for authentication checks. Route meta fields carry per-route configuration.

12. Next Chapter Recommendation

In Chapter 16: State Management with Pinia, we add global reactive state to our Vue app — the modern replacement for Vuex, with a much simpler API.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·