skip to content

Nested & Named Views

Nesting routes with children and a RouterView per level, naming parallel views, and wrapping RouterView's slot in Transition or KeepAlive. Interviewers ask what remounts when.

on this pageshow

explore

questions

5

In Vue Router 5, how do you build a /settings area whose sidebar layout stays on screen while /settings/profile and /settings/security swap inside it?

level: juniorimportance: must knowfreq 64%

answer

  1. children array on the parent record
  2. a RouterView inside the layout
  3. relative child paths
  4. leading slash means root
  5. depth picks the matched record

basics

~10 s

Give the /settings record a SettingsLayout component and a children array with relative paths like 'profile'; SettingsLayout renders the sidebar plus its own RouterView, which shows the matched child while the layout stays mounted.

solid answer

~40 s

Make `/settings` a parent record with `component: SettingsLayout` and `children: [{ path: 'profile', ... }, { path: 'security', ... }]`. Child paths are **relative**: they join onto the parent to form `/settings/profile`; a child path starting with `/` is treated as a root path instead. `SettingsLayout` renders the sidebar and a `<RouterView />`. Each `RouterView` renders one level of `route.matched`: the one in `App.vue` shows `SettingsLayout`, the nested one shows the matched child. Switching from profile to security changes only the inner level, so the layout instance and the sidebar's local state survive. Visiting plain `/settings` matches only the parent, so the inner `RouterView` renders nothing until you add an empty-path child.

code

ts · 16 lines
ts
import { createRouter, createWebHistory } from 'vue-router'
import SettingsLayout from '@/views/settings/SettingsLayout.vue'

export const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/settings',
      component: SettingsLayout, // renders <SettingsSidebar /> and <RouterView />
      children: [
        { path: 'profile', component: () => import('@/views/settings/SettingsProfile.vue') },
        { path: 'security', component: () => import('@/views/settings/SettingsSecurity.vue') },
      ],
    },
  ],
})

go deeper

for a junior

Know the children option, relative child paths, and that the parent component needs its own RouterView.

for a middle

Explain how RouterView depth maps onto route.matched and why the layout instance survives child switches.

for a senior

Spot designs that remount layouts, such as flattened routes or over-broad keys, and keep shared data loading at the right level.

for a principal

Decide which URL segments map to persistent layouts so each area keeps state and loads shared data once.

## The shape of a nested route In Vue Router 5, nesting is declared with the `children` option, which is just another array of route records: ```ts const routes = [ { path: '/settings', component: SettingsLayout, children: [ { path: 'profile', component: SettingsProfile }, { path: 'security', component: SettingsSecurity }, ], }, ] ``` - A child's `path` is **relative** to its parent: `profile` becomes `/settings/profile`. The router joins them with a slash. - A child path that **starts with `/`** is treated as a root path. `{ path: '/security' }` under `/settings` still renders inside `SettingsLayout`, but its URL is `/security`. The docs present this as a feature: component nesting without URL nesting. - Children can have children of their own; the depth is unlimited. ## Where each component renders The layout component contains its own `RouterView`: ```vue <template> <div class="settings"> <SettingsSidebar /> <RouterView /> </div> </template> ``` When the URL is `/settings/profile`, `route.matched` holds two records: the parent and the child. Each `RouterView` knows its **depth** by counting the `RouterView` ancestors above it: | RouterView | Depth | Renders | |---|---|---| | in `App.vue` | 0 | `SettingsLayout` | | inside `SettingsLayout` | 1 | `SettingsProfile` | A `RouterView` whose depth has no matched record renders nothing. That is why visiting plain `/settings` shows the sidebar with an empty pane: only the parent matched. ## What stays mounted Going from `/settings/profile` to `/settings/security`: 1. Depth 0 still matches the same parent record and the same `SettingsLayout` component, so Vue **patches** that instance; its `setup` does not run again. 2. Depth 1 changes from `SettingsProfile` to `SettingsSecurity`, a different component, so the old one unmounts and the new one mounts. The sidebar's scroll position, an expanded group or a search box inside `SettingsLayout` therefore survive child switches. Data that the layout loads once, such as the user's account summary, is loaded once for the whole area. ## Common mistakes - **Forgetting the nested `RouterView`.** The URL changes, `route.matched` is right, but nothing renders in the pane because no `RouterView` exists at depth 1. - **Writing child paths with the parent prefix.** `{ path: 'settings/profile' }` under `/settings` produces `/settings/settings/profile`. - **Accidental root paths.** `{ path: '/profile' }` in `children` silently leaves the settings URL space. - **Flattening instead of nesting.** Declaring `/settings/profile` and `/settings/security` as top-level records, each rendering the sidebar itself, remounts the sidebar on every switch, because depth 0 now holds a different component each time. - **Keying the top-level view on the full path.** A `:key` bound to the whole path on the outer view remounts the layout on every child switch, undoing the benefit. ## Seeing the levels in code `route.matched` lists the records that matched, from the outermost to the innermost: - On `/settings/profile` it holds the settings parent and the profile child. - Each entry carries the record's path pattern, its components and its meta fields. - Layout-level UI such as a breadcrumb or a section title can walk this list instead of parsing the URL. - `route.matched.length` tells the layout whether any section is selected, which is how it can show a hint in an empty pane. A unit test can prove the layout persists: mount the app with a memory-history router, push `/settings/profile`, expand a sidebar group, push `/settings/security`, and assert the group is still expanded while the security pane rendered. ## Beyond one level - Params from the parent path are available in every child: under `/settings/:section`, a child reads `route.params.section`. - A parent record may omit its component entirely; its `RouterView` level is then skipped, which groups routes under a prefix without adding a layout. - Several panes at the same level, such as a list and a detail beside each other, use **named views** rather than more nesting. - The empty-path child fills the pane when the parent's own URL is visited. - Each extra level needs its own `RouterView` inside the component of the level above; three levels of `children` mean three `RouterView`s in three components. - Named routes work at any depth: `router.push({ name: 'settings-profile' })` builds `/settings/profile` from the joined paths, so links survive a later rename of the parent's path.

  • Why does the sidebar keep its expanded state when switching from profile to security?
    Both URLs match the same parent record at depth 0, so the outer `RouterView` renders the same `SettingsLayout` component and Vue patches the existing instance. Only the depth-1 `RouterView` swaps components. Local state in the layout, including the sidebar's, is never destroyed.
  • A teammate puts { path: '/billing', component: SettingsBilling } inside the settings children. What happens?
    A child path starting with `/` is absolute, so the URL is `/billing`, not `/settings/billing`, but it still renders inside `SettingsLayout`. That is useful for nesting components without nesting URLs; if it was meant to be a settings URL, drop the leading slash.

saying these in an interview costs you the question

  • Child paths must repeat the parent prefix, like 'settings/profile'.
  • A child path starting with / still gets the parent prefix.
  • The layout remounts on every child switch, so its state is always lost.
  • One top-level RouterView renders every level of nested routes.
  • Visiting /settings automatically renders the first child.
open as a page

In Vue Router 5, why must Transition and KeepAlive go inside RouterView's v-slot instead of wrapping RouterView, and what does the slot expose?

level: middleimportance: must knowfreq 55%

basics

~10 s

Transition and KeepAlive must act on the route component, not on RouterView; the v-slot passes that component as Component, plus the displayed route, and you render it with <component :is> inside them.

open as a page

In Vue Router 5, why does push({ name: 'settings' }) show an empty settings pane while reloading /settings shows the overview child?

level: middleimportance: should knowfreq 42%

basics

~20 s

The name sits on the parent, so a named push matches only the parent and its RouterView stays empty, while a path match prefers the empty-path child. Put the name on the empty-path child instead.

open as a page

In Vue Router 5, how do named views show a mail list and message detail side by side, and how do they differ from nesting?

level: middleimportance: should knowfreq 38%

basics

~20 s

A record's components map (default, detail, ...) fills several RouterViews at the same depth, each selected by its name prop; nested routes instead stack one view inside another. Views without a component for the route render nothing.

open as a page

In Vue Router 5, the mail client's fade never plays between messages, and after adding :key="route.fullPath" the settings sidebar resets on every tab; how do keys and nesting explain both?

level: seniorimportance: should knowfreq 36%

basics

~20 s

The same component is reused between messages, so Transition has nothing to animate until a key forces a remount. A fullPath key on the outer view remounts the layout on every child switch; key each level on what it owns.

open as a page