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:
<!-- App.vue -->
<template>
<router-view />
</template><!-- User.vue -->
<template>
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view />
</div>
</template>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
childrenoption to define nested routes childrenis an array of route objects, just like the mainroutesarray- You can nest views as deeply as needed
Path Resolution
Important: Nested paths that start with / will be treated as root paths:
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:
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:
<!-- 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><!-- 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><!-- UserHome.vue -->
<template>
<div>
<h3>User Home</h3>
<p>Welcome to user {{ $route.params.id }}'s dashboard!</p>
</div>
</template><!-- UserProfile.vue -->
<template>
<div>
<h3>User Profile</h3>
<p>Profile information for user {{ $route.params.id }}</p>
</div>
</template><!-- 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:
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.
Navigation with Named Routes
// 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:
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:
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:
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
// 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:
<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
<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
- Forgetting the nested
<router-view>- Child routes won't render without it - Using absolute paths in children - Starts with
/ignores parent path - Not providing default child routes - Empty parent routes show nothing
- Deeply nested parameter access - All parameters are available in
$route.params
Next Steps
- Learn about Programmatic Navigation for dynamic routing
- Explore Named Routes for cleaner navigation
- Understand Route Guards for nested route protection