Async component resolved under <Suspense> is mounted with the Suspense's namespace, so SVG children become HTML elements
Vue version
3.5.43 (also reproduces on the SFC Playground's latest main build)
Link to minimal reproduction
Steps to reproduce
Open the reproduction. It renders the same component — whose root is a single <path> — inside an <svg> three times:
- a sync component, under
<Suspense>; - an async component (
defineAsyncComponent), without<Suspense>; - the same async component, under
<Suspense>.
The <pre> at the bottom prints each <path>'s namespaceURI.
What is expected?
The line is drawn in all three <svg>s, and every <path> has the SVG namespace (http://www.w3.org/2000/svg).
What is actually happening?
Case 3 draws nothing: its <path> is created as an HTML element (http://www.w3.org/1999/xhtml) inside the <svg>.
1. sync + Suspense: http://www.w3.org/2000/svg
2. async, no Suspense: http://www.w3.org/2000/svg
3. async + Suspense: http://www.w3.org/1999/xhtmlWith suspensible: false on the async component, the path is an SVG element again.
System Info
System: macOS 26.6.2 (Darwin 25.6)
Browsers: Chrome
npmPackages: vue 3.5.43Any additional comments?
Under a <Suspense>, a suspensible async component's setup is registered as a dependency of the boundary (suspense.registerDep). When it resolves, registerDep calls setupRenderEffect(instance, vnode, parentNode(...), anchor, suspense, namespace, optimized) where namespace is the one createSuspenseBoundary received — the namespace of the <Suspense> itself, not the one of the position the component was rendered at (svg here). The component's subtree is then mounted with the boundary's namespace.
We hit it with graph libraries (Vue Flow custom edge types) in an app that registers its components with defineAsyncComponent and renders pages under a <Suspense>: the edges' <path>s were in the DOM, with valid coordinates, but not drawn. Reproduced outside the browser as well (jsdom + createApp, Vue 3.5.43), with the same result.
Source: vuejs/core