In Vue Router 5, why does push({ name: 'settings' }) show an empty settings pane while reloading /settings shows the overview child?
answer
- empty-path child fills the pane
- the name decides which record matches
- path matching prefers the child
- name the child, not the parent
- a parent may have no component
basics
~20 sThe 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.
solid answer
~40 sAn **empty-path child**, `{ path: '', component: SettingsOverview }`, renders in the layout's `RouterView` when the parent's own URL is visited. Loading `/settings` by path matches that child, because a child with the same path is ranked ahead of its parent. A **named** push goes straight to the record carrying the name; if `settings` is on the parent, only the parent matches and the pane is empty. The docs' advice is to **name the child**: `children: [{ path: '', name: 'settings', component: SettingsOverview }]`, so every way of reaching `/settings` shows the overview. In development the router warns when a named parent has an unnamed empty-path child. Since 4.5 a parent and child cannot share a name, and since 4.1 a parent may omit its component to group routes without a layout.
code
ts · 11 linesconst routes = [
{
path: '/settings',
component: () => import('@/views/settings/SettingsLayout.vue'),
children: [
// the name lives on the empty-path child
{ path: '', name: 'settings', component: () => import('@/views/settings/SettingsOverview.vue') },
{ path: 'profile', name: 'settings-profile', component: () => import('@/views/settings/SettingsProfile.vue') },
],
},
]go deeper
Know that an empty-path child fills the layout's pane when the parent URL is visited.
Explain why path matching picks the empty child while a named push picks the named record, and name the child.
Use component-less parents for shared prefixes and options, and avoid relative redirects inside children.
Set naming conventions for nested route tables so every section is reachable by name the same way it is by URL.
## Two routes, one URL With an empty-path child, two records produce the same path: ```ts { path: '/settings', name: 'settings', // the problem component: SettingsLayout, children: [ { path: '', component: SettingsOverview }, { path: 'profile', component: SettingsProfile }, ], } ``` - The parent's path is `/settings`. - The empty child's path joins to `/settings` too. Both are matchable, and they reach the screen by different mechanisms. ## Matching by path versus by name | Navigation | Record matched | `route.matched` | Pane shows | |---|---|---|---| | reload or paste `/settings` | the empty-path child | parent, child | `SettingsOverview` | | `push('/settings')` | the empty-path child | parent, child | `SettingsOverview` | | `push({ name: 'settings' })` | the parent itself | parent | nothing | When matching by **path**, the router keeps records sorted by score, and a child whose score equals its ancestor's is inserted **ahead** of that ancestor. The empty-path child therefore wins. When navigating by **name**, there is no matching step: the router looks up the record with that name. If the name is on the parent, the parent is the deepest matched record, and the layout's `RouterView` has nothing at depth 1 to render. The docs describe exactly this: naming the parent lets you navigate to it without the nested route, but **a reload always shows the nested child**, because the reload is a path navigation. ## The fix: name the child ```ts children: [ { path: '', name: 'settings', component: SettingsOverview }, { path: 'profile', name: 'settings-profile', component: SettingsProfile }, ] ``` Now `push({ name: 'settings' })` and a reload both land on the overview. The router backs this up in two ways: 1. In development it **warns** when a route with a name has a child with no name, an empty path and no children of its own, since using the parent's name will not render that child. 2. Since Vue Router 4.5 it **throws in development** when a child or descendant is given the same name as an ancestor, so you cannot fix this by copying the name to both. Keep a name on the parent only if you deliberately want a way to show the layout with an empty pane. ## The empty-path child's URL A named empty-path child resolves **without** a trailing slash: `router.resolve({ name: 'settings' }).href` is `/settings`. That has a side effect for relative redirects inside children: `{ path: '', redirect: 'profile' }` resolves relative to `/settings`, which gives `/profile`, not `/settings/profile`. Redirect to a name, or to an absolute path, instead. ## A parent with no component Since Vue Router 4.1, a parent record may omit `component` and `components`: ```ts { path: '/settings/admin', meta: { requiresAdmin: true }, children: [ { path: 'users', component: AdminUsers }, { path: 'audit', component: AdminAudit }, ], } ``` - The parent still contributes its **path prefix**, and anything else declared on it, such as meta fields or a per-route guard, applies to all children. - `RouterView` **skips** a matched record with no components, so the children render in the `RouterView` that would otherwise have shown the parent. No extra layout level is added. - A parent with no component, no name and no redirect is not itself matchable: visiting `/settings/admin` directly falls through to other routes, such as a catch-all, unless you add an empty-path child. ## Proving it in a test `router.resolve()` shows the difference without mounting anything: 1. `router.resolve({ name: 'settings' }).matched.length` is `1` when the name is on the parent: only the layout record. 2. `router.resolve('/settings').matched.length` is `2`: the layout and the empty-path child. 3. After moving the name to the child, both calls return `2`. A test asserting that the two agree catches the mistake the moment someone moves a name. ## Summary of rules - Fill a layout's default pane with an empty-path child. - Put the name on that child. - Never reuse a name between a parent and its descendants. - Omit the parent's component when you only need a shared prefix or shared route options.
- Inside the settings children, { path: '', redirect: 'profile' } sends users to /profile. Why?An empty-path child resolves to `/settings` without a trailing slash, and the relative location `profile` against `/settings` is `/profile`, just as a relative link would resolve. Redirect to a name, `{ name: 'settings-profile' }`, or to the absolute `/settings/profile`.
- When is a parent record without a component the right tool?When several routes share a URL prefix and route-level options, such as meta fields or a guard, but no shared layout. `RouterView` skips the component-less level, so children render where the parent would have. If the routes do share visible chrome, give the parent a layout component instead.
saying these in an interview costs you the question
- Named navigation and path navigation always match the same record.
- Put the name on the parent so the whole section is reachable by name.
- Giving the parent and the empty-path child the same name fixes it.
- A parent without a component cannot have children.
- An empty-path named child resolves to /settings/ with a trailing slash.