skip to content

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%

answer

  1. wrap the route component, not the view
  2. v-slot gives Component and route
  3. component :is renders the vnode
  4. wrapping RouterView warns in dev
  5. transition outside, keep-alive inside

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.

solid answer

~40 s

Since Vue Router 4, `RouterView` exposes a default slot with `{ Component, route }`: `Component` is the rendered route component and `route` is the location this view displays. Wrapping it lets `<Transition>` see the route component enter and leave, and lets `<KeepAlive>` cache route component instances. Put `<RouterView>` itself inside `<Transition>` or `<KeepAlive>` and they act on the `RouterView` wrapper instead, which in Vue 3 no longer passes through to the route component; the router warns about it in development. The documented nesting is `<RouterView v-slot="{ Component }"><Transition><KeepAlive><component :is="Component" /></KeepAlive></Transition></RouterView>`. The slot also lets you put a template ref on the route component itself, which a ref on `RouterView` cannot do.

code

vue · 11 lines
vue
<!-- MailLayout.vue -->
<template>
  <MailFolders />
  <RouterView v-slot="{ Component, route }">
    <Transition name="fade" mode="out-in">
      <KeepAlive :max="10">
        <component :is="Component" :key="route.path" />
      </KeepAlive>
    </Transition>
  </RouterView>
</template>

go deeper

for a junior

Know the v-slot pattern with Component and <component :is> for adding a transition to route changes.

for a middle

Explain why wrapping RouterView fails in Vue 3, what the slot exposes, and the Transition then KeepAlive nesting order.

for a senior

Handle first-load animations, empty matches, refs to route components and which nested level to animate or cache.

for a principal

Set app-wide conventions for route transitions and caching so layouts stay stable and memory stays bounded.

## What the slot is `<RouterView>` can render its matched component directly, or hand it to you through a **scoped default slot**: ```vue <RouterView v-slot="{ Component, route }"> <component :is="Component" /> </RouterView> ``` The slot receives two values: - `Component`: the route component, already created as a vnode with its route props. `<component :is>` accepts a vnode as well as a component, so it renders it as-is. - `route`: the route location this `RouterView` displays, which is the place to read `route.meta` or `route.path` for transitions and keys. Without any wrapper, the slot form renders the same thing as a plain `<RouterView />`. Its value is what you can put around the component. ## Why wrapping RouterView no longer works In Vue Router 3, on Vue 2, you wrote `<transition><router-view /></transition>` and `<keep-alive><router-view /></keep-alive>`. That relied on `router-view` being a functional component that disappeared from the tree, so the wrapper saw the route component directly. In Vue 3 `RouterView` is a normal component, so the wrapper sees `RouterView` itself: - `<KeepAlive>` around `RouterView` would cache the `RouterView` instance, not each route component. - `<Transition>` around `RouterView` sees one child that never changes, so route switches do not animate as intended. The router detects `RouterView` placed directly inside `KeepAlive` or a `Transition` and prints a development warning that shows the slot pattern instead. ## The documented patterns 1. **Transition only**: `<Transition name="fade">` around `<component :is="Component" />` animates each route change. 2. **KeepAlive only**: route component instances are cached when you leave and restored when you return. 3. **Both**: `Transition` on the outside, `KeepAlive` inside it, then the component. The order matters: the transition animates the element that `KeepAlive` shows or hides. | Placement | What it acts on | Result | |---|---|---| | `<Transition>` around `<RouterView>` | the RouterView | dev warning, no route animation | | `<KeepAlive>` around `<RouterView>` | the RouterView | dev warning, route components not cached | | inside the `v-slot` | the route component | works as documented | ## Details that come up - **The first load animates.** Because the initial navigation is asynchronous, the app renders once with no route and then with the first one, so the enter transition plays on load as if `appear` were set. Awaiting the router's readiness before mounting avoids it. - **Empty slot values.** When nothing matches at this depth, the slot still runs with `Component` undefined; `<component :is>` then renders nothing, and `Transition` treats that as a leave. - **Template refs.** `ref` on `<component :is="Component">` inside the slot reaches the route component; a `ref` on `<RouterView>` holds the `RouterView` instance. - **Extra props and slots.** You can pass props or slot content to `<component :is>`, but every route component rendered there receives them, so the docs discourage it for data. - **KeepAlive filters** match component names, and how many instances it keeps is controlled by its own props; both belong to `KeepAlive` itself. ## Displaying a route other than the current one `RouterView` also accepts a `route` prop. When it is set, the view renders that location instead of the current one, and the slot's `route` is that location; nested views below it follow the same location. A common use is a modal opened over a page: the page's `RouterView` keeps rendering the previous location while the modal's view renders the new one. It is an advanced pattern, but it explains why the slot hands you `route` rather than expecting you to call `useRoute()`. ## Choosing what to cache - Cache **list panes** whose filters, scroll position and loaded pages should survive a trip into a detail page and back. - Do not cache **per-item detail panes** unless their number is bounded; each cached instance holds its data in memory. - Refresh cached views on return with the activation hook that `KeepAlive` provides, so a cached list does not show stale data. ## Nested views Each `RouterView` level has its own slot. A layout can animate its inner pane with one transition while the top level uses another, or keep only the inner pane alive. Wrapping only the level that changes keeps the persistent layout out of the animation.

  • A migrated Vue 2 app still has <keep-alive><router-view /></keep-alive>. What does the user notice, and what does the console say?
    Route components are no longer cached, so forms and scroll positions reset on return, because `KeepAlive` wraps the `RouterView` instead of the route components. In development the router warns that `router-view` can no longer be used directly inside `keep-alive` and shows the `v-slot` pattern to use instead.
  • How do you call a method exposed by the current route component from its layout?
    Put a template ref on `<component :is="Component" ref="current" />` inside the `RouterView` slot; the ref then holds the route component's instance. A ref on `<RouterView>` would hold the `RouterView` instance, which does not expose the route component's methods.

saying these in an interview costs you the question

  • <transition><router-view /></transition> still works in Vue Router 5.
  • KeepAlive around RouterView caches every route component.
  • The slot's Component is a component name string.
  • KeepAlive should wrap Transition, not the other way round.
  • A ref on RouterView gives you the route component's instance.