skip to content

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%

answer

  1. same component means no enter or leave
  2. a key forces a new instance
  3. the slot route is the whole route
  4. key each level on what it owns
  5. per-route names from meta

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.

solid answer

~40 s

`/mail/inbox/1` and `/mail/inbox/2` render the same component in the same view, so Vue patches it and `<Transition>` has no enter or leave to animate. The docs' fix is `<component :is="Component" :key="route.path" />` in the slot, which gives each URL its own instance. The catch is that the slot's `route` is the **whole current route** at every depth. A `:key="route.fullPath"` on the top-level view changes whenever any child, param or query changes, so `SettingsLayout` is destroyed and remounted on every tab switch, and the sidebar loses its state. Key each level on what it owns: the pane that shows one message on `route.params.messageId`, the outer layout on nothing. Per-route transition names come from `route.meta.transition` in the same slot. Keys inside `KeepAlive` also multiply cache entries.

code

vue · 8 lines
vue
<!-- App.vue: no key, the settings layout survives child switches -->
<template>
  <RouterView v-slot="{ Component, route }">
    <Transition :name="route.meta.transition || 'fade'" mode="out-in">
      <component :is="Component" />
    </Transition>
  </RouterView>
</template>

go deeper

for a junior

Remember that a transition only plays when the component changes, and a key can force that change.

for a middle

Explain patch versus remount, what the slot's route contains, and per-route transition names from meta.

for a senior

Place keys per level on what each view owns, avoid fullPath keys on layouts, and bound KeepAlive caches when views are keyed.

for a principal

Define where the app accepts remounts and where it guarantees persistence, and encode it in shared layout components.

## Two symptoms, one mechanism Vue decides between **patching** and **remounting** by comparing the component type and the `key` of the vnode it renders. `RouterView` itself adds no key. So: - if a navigation leaves the same component at a view's depth, the instance is **reused**; - if the component changes, or you give it a new `key`, the old instance **unmounts** and a new one **mounts**. `<Transition>` animates only enter and leave, so a reused instance plays nothing. ## Symptom 1: no fade between messages On `/mail/inbox/1` to `/mail/inbox/2`, the detail view renders `MessageDetail` both times. Vue patches it, the text changes in place, and the fade never runs. The router docs' fix is a key in the slot: ```vue <RouterView v-slot="{ Component, route }"> <Transition name="fade" mode="out-in"> <component :is="Component" :key="route.path" /> </Transition> </RouterView> ``` Now each path gets a distinct vnode key, so the old message leaves and the new one enters. The price is a full remount per message: local state resets and `setup` runs again. ## Symptom 2: the settings sidebar resets The same idea applied at the **top level** breaks the layout. The slot's `route` is the complete current route, the same object at every depth. On `App.vue`'s view: 1. `/settings/profile` to `/settings/security` changes `route.fullPath`. 2. The key on depth 0 changes, so Vue remounts `SettingsLayout`, even though the parent record did not change. 3. The sidebar, rendered by the layout, loses its scroll, expanded groups and any data loaded in `setup`. A key bound to `$route.fullPath` directly on a `<RouterView>` element has the same effect on everything under it. ## Choosing keys per level | View | Key | Remounts when | |---|---|---| | top level in `App.vue` | none | the top-level component changes | | mail detail pane | `route.params.messageId` | another message opens | | settings pane | none, or `route.path` | the section changes | | any level | `route.fullPath` | anything changes, including query and hash | The rule: **key a view on what that view displays**. `fullPath` includes query and hash, so it also remounts on a sort change or an anchor jump, which is almost never wanted. ## Per-route transitions The same slot can choose a transition per route: ```vue <RouterView v-slot="{ Component, route }"> <Transition :name="route.meta.transition || 'fade'"> <component :is="Component" /> </Transition> </RouterView> ``` - Each record declares `meta: { transition: 'slide-left' }`; the view falls back to `fade`. - A global `afterEach` hook can instead compute the name from the depth of `to` and `from`, sliding left when going deeper and right when going back up, as the router guide shows. - `route.meta` merges the meta of every matched record, so a parent's transition name applies to its children unless a child overrides it. ## Keys and KeepAlive `KeepAlive` caches by vnode key when there is one, else by component. Keying a cached view on `route.path` therefore creates **one cache entry per URL**: every message opened stays in memory until evicted. Bound it with `KeepAlive`'s `max`, key on something coarser, or skip caching for per-item panes. ## What the user feels either way | Behaviour | Reuse, no key change | Remount, new key | |---|---|---| | Transition | none | enter and leave play | | Local state | kept | reset | | Data loading | must watch params | runs in `setup` again | | Scroll inside the view | kept | reset | | Focus inside the view | kept | lost | | Cost | a patch | a full unmount and mount | Neither is wrong. A message pane benefits from a remount: fresh state per message and a visible transition. A settings layout benefits from reuse: the sidebar should never flicker. The skill is choosing per level. ## A debugging routine 1. For each view, write down which component it renders before and after the navigation. 2. Same component and no key change: expect reuse, no transition, no `onMounted`. 3. Different component or changed key: expect unmount and mount, a transition, fresh local state. 4. If the result surprises you, look for a key using `route` at a level that does not own that part of the URL.

  • Why is route.fullPath usually the wrong key even for a leaf view?
    `fullPath` includes the query and hash, so changing a sort order, a filter or jumping to an anchor remounts the view and replays its transition. Key on the params that identify what the view shows, such as `messageId`, or on `route.path` when the query is irrelevant to it.
  • The team wants a slide-left animation when drilling into a message and slide-right when going back. Where does that logic go?
    Compute the name per navigation and read it in the slot. The router guide uses a global `afterEach` that compares the depth of `to.path` and `from.path` and writes `to.meta.transition`; the view binds `<Transition :name="route.meta.transition">`. Keep the key on the pane that changes so the animation has an enter and a leave.

saying these in an interview costs you the question

  • Transition always animates route changes, even when the component is reused.
  • The slot's route is only the part of the route this level matched.
  • Keying the top-level view on fullPath is harmless for nested layouts.
  • A key inside KeepAlive has no effect on how many instances are cached.
  • Vue Router adds a key to RouterView's component automatically.