Skip to content

Nested Routes

Build complex application layouts with nested route configurations and multiple router-view components.

Introduction

Real applications often have UIs composed of components that are nested multiple levels deep. It's very common that the segments of a URL correspond to a certain structure of nested components.

For example:

/user/johnny/profile                   /user/johnny/posts 
┌──────────────────┐                  ┌──────────────────┐
│ User             │                  │ User             │
│ ┌──────────────┐ │                  │ ┌──────────────┐ │
│ │ Profile      │ │  ────────────>   │ │ Posts        │ │
│ │              │ │                  │ │              │ │
│ └──────────────┘ │                  │ └──────────────┘ │
└──────────────────┘                  └──────────────────┘

With Vue Router, you can express this relationship using nested route configurations.

Basic Nested Routes

Let's start with a simple app structure:

vue
<!-- App.vue -->
<template>
  <router-view />
</template>
vue
<!-- User.vue -->
<template>
  <div class="user">
    <h2>User {{ $route.params.id }}</h2>
    <router-view />
  </div>
</template>
javascript
import User from './User.vue'
import UserProfile from './UserProfile.vue'
import UserPosts from './UserPosts.vue'

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        // UserProfile will be rendered inside User's <router-view>
        // when /user/:id/profile is matched
        path: 'profile',
        component: UserProfile,
      },
      {
        // UserPosts will be rendered inside User's <router-view>
        // when /user/:id/posts is matched
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
]

Key Concepts

Multiple Router Views

  • The top-level <router-view> renders components matched by top-level routes
  • Rendered components can contain their own nested <router-view>
  • Each <router-view> renders the component for its corresponding nesting level

Children Configuration

  • Use the children option to define nested routes
  • children is an array of route objects, just like the main routes array
  • You can nest views as deeply as needed

Path Resolution

Important: Nested paths that start with / will be treated as root paths:

javascript
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      // ✅ Relative path - becomes /user/:id/profile
      { path: 'profile', component: UserProfile },
      
      // ❌ Absolute path - becomes /settings (ignores parent)
      { path: '/settings', component: Settings },
    ],
  },
]

Default Child Routes

When visiting /user/eduardo, nothing will be rendered inside User's <router-view> because no nested route is matched. To render something by default, provide an empty nested path:

javascript
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      // UserHome will be rendered inside User's <router-view>
      // when /user/:id is matched
      { path: '', component: UserHome },
      
      // Other sub routes
      { path: 'profile', component: UserProfile },
      { path: 'posts', component: UserPosts },
    ],
  },
]

Complete Example

Here's a complete example of a user dashboard with nested routes:

vue
<!-- App.vue -->
<template>
  <div id="app">
    <nav>
      <router-link to="/user/1">User 1</router-link>
      <router-link to="/user/2">User 2</router-link>
    </nav>
    <router-view />
  </div>
</template>
vue
<!-- User.vue -->
<template>
  <div class="user">
    <h2>User {{ $route.params.id }}</h2>
    <nav class="user-nav">
      <router-link :to="`/user/${$route.params.id}`">Home</router-link>
      <router-link :to="`/user/${$route.params.id}/profile`">Profile</router-link>
      <router-link :to="`/user/${$route.params.id}/posts`">Posts</router-link>
    </nav>
    <router-view />
  </div>
</template>

<style scoped>
.user {
  padding: 20px;
}

.user-nav {
  margin: 20px 0;
}

.user-nav a {
  margin-right: 10px;
  padding: 5px 10px;
  border: 1px solid #ccc;
  text-decoration: none;
}

.user-nav a.router-link-active {
  background-color: #007bff;
  color: white;
}
</style>
vue
<!-- UserHome.vue -->
<template>
  <div>
    <h3>User Home</h3>
    <p>Welcome to user {{ $route.params.id }}'s dashboard!</p>
  </div>
</template>
vue
<!-- UserProfile.vue -->
<template>
  <div>
    <h3>User Profile</h3>
    <p>Profile information for user {{ $route.params.id }}</p>
  </div>
</template>
vue
<!-- UserPosts.vue -->
<template>
  <div>
    <h3>User Posts</h3>
    <p>Posts by user {{ $route.params.id }}</p>
  </div>
</template>

Named Nested Routes

When dealing with named routes, you usually name the children routes:

javascript
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      // Only the child route has a name
      { path: '', name: 'user', component: UserHome },
      { path: 'profile', name: 'user-profile', component: UserProfile },
      { path: 'posts', name: 'user-posts', component: UserPosts },
    ],
  },
]

This ensures navigating to /user/:id will always display the nested route.

javascript
// Navigate to user home
router.push({ name: 'user', params: { id: '123' } })

// Navigate to user profile
router.push({ name: 'user-profile', params: { id: '123' } })

Advanced Patterns

Omitting Parent Components

You can group routes with a common path prefix without nesting components by omitting the component option from the parent route:

javascript
const routes = [
  {
    path: '/admin',
    // No component specified
    children: [
      { path: '', component: AdminOverview },
      { path: 'users', component: AdminUserList },
      { path: 'users/:id', component: AdminUserDetails },
    ], 
  },
]

The top-level <router-view> will skip the parent and render the child component directly.

Multiple Levels of Nesting

You can nest routes as deeply as needed:

javascript
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        path: 'posts',
        component: UserPosts,
        children: [
          { path: '', component: PostList },
          { path: ':postId', component: PostDetail },
          { path: ':postId/edit', component: PostEdit },
        ],
      },
    ],
  },
]

This creates routes like:

  • /user/123/posts → PostList
  • /user/123/posts/456 → PostDetail
  • /user/123/posts/456/edit → PostEdit

Best Practices

1. Organize Route Structure

Keep your route structure logical and mirror your component hierarchy:

javascript
const routes = [
  {
    path: '/',
    component: Layout,
    children: [
      { path: '', component: Home },
      {
        path: 'dashboard',
        component: Dashboard,
        children: [
          { path: '', component: DashboardOverview },
          { path: 'analytics', component: Analytics },
          { path: 'settings', component: Settings },
        ],
      },
    ],
  },
]

2. Use Named Routes for Complex Navigation

javascript
// Instead of building paths manually
router.push(`/user/${userId}/posts/${postId}/edit`)

// Use named routes
router.push({
  name: 'post-edit',
  params: { userId, postId }
})

3. Handle Loading States

Show loading indicators while nested components load data:

vue
<template>
  <div class="user">
    <h2>User {{ $route.params.id }}</h2>
    <nav class="user-nav">
      <!-- Navigation links -->
    </nav>
    <Suspense>
      <router-view />
      <template #fallback>
        <div class="loading">Loading...</div>
      </template>
    </Suspense>
  </div>
</template>

4. Validate Parameters at Each Level

vue
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

const isValidUserId = computed(() => {
  return /^\d+$/.test(route.params.id)
})
</script>

<template>
  <div v-if="isValidUserId">
    <!-- Valid user content -->
    <router-view />
  </div>
  <div v-else>
    <h2>Invalid User ID</h2>
    <router-link to="/">Go Home</router-link>
  </div>
</template>

Common Pitfalls

  1. Forgetting the nested <router-view> - Child routes won't render without it
  2. Using absolute paths in children - Starts with / ignores parent path
  3. Not providing default child routes - Empty parent routes show nothing
  4. Deeply nested parameter access - All parameters are available in $route.params

Next Steps

🚀 Vue Router - 让前端路由变得简单而强大 | 构建现代化单页应用的最佳选择