vuejs/vue-router · info
In Vue Router 4, the v-slot API will by default wrap its con
Error message
In Vue Router 4, the v-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:
<router-link v-slot="{ navigate, href }" custom></router-link>
What it means
When <router-link> receives a scoped slot with a number of root children other than exactly one, it cannot render them as a single replacement element, so in dev it warns that it will wrap the content in a <span> instead. The library then renders h('span', {}, scopedSlot) to keep the DOM valid.
Source
Thrown at src/components/link.js:116
on[this.event] = handler
}
const data: any = { class: classes }
const scopedSlot =
!this.$scopedSlots.$hasNormal &&
this.$scopedSlots.default &&
this.$scopedSlots.default({
href,
route,
navigate: handler,
isActive: classes[activeClass],
isExactActive: classes[exactActiveClass]
})
if (scopedSlot) {
if (process.env.NODE_ENV !== 'production' && !this.custom) {
!warnedCustomSlot && warn(false, 'In Vue Router 4, the v-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:\n<router-link v-slot="{ navigate, href }" custom></router-link>\n')
warnedCustomSlot = true
}
if (scopedSlot.length === 1) {
return scopedSlot[0]
} else if (scopedSlot.length > 1 || !scopedSlot.length) {
if (process.env.NODE_ENV !== 'production') {
warn(
false,
`<router-link> with to="${
this.to
}" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
)
}
return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
}
}
if (process.env.NODE_ENV !== 'production') {View on GitHub (pinned to 680ccc68c5)
Solutions
- Wrap the slot content in a single root element (e.g. a <span> or <div>).
- Remove empty/comment-only content from the v-slot body.
- Use the custom prop with exactly one functional child or restructure to a single component root.
Example fix
// before (two roots)
<router-link to="/x" custom v-slot="{ href, navigate }"><span>A</span><span>B</span></router-link>
// after (single root)
<router-link to="/x" custom v-slot="{ href, navigate }"><span><span>A</span><span>B</span></span></router-link> Defensive patterns
Strategy: validation
Validate before calling
// template-time check: use custom whenever v-slot is present on router-link // e.g. ESLint rule idea: flag <router-link v-slot without custom
Prevention
- Always pair v-slot on <router-link> with the custom prop.
- Drop the scoped slot if you want the default anchor rendering.
- Treat this warning as a migration checklist item when moving to vue-router 4.
- Keep dev warnings enabled during development to catch these early.
When it happens
Trigger: <router-link to="..." v-slot="{ ... }"> containing zero children (empty slot) or more than one root node (e.g. two sibling elements or text plus an element), in dev mode with the functional/normal slot-length check (scopedSlot.length).
Common situations: Adding a comment node or whitespace-producing extra root inside the v-slot; components returning fragments (multiple roots); accidentally rendering an empty v-slot body.
Related errors
- [vue-router]: Missing current instance. ${method}() must be
- Failed to resolve async component ${key}: ${reason}
- <router-link> with to="${this.to}" is trying to use a scoped
- <router-link>'s tag prop is deprecated and has been removed
- [vue-router] ${message}
AI-assisted analysis of vuejs/vue-router@680ccc68c5 (2026-09-02).
Data as JSON: /api/errors/7db705e914c2af77.
Report an issue: GitHub.