Skip to content

fix(MDCRenderer): keep async component identity stable across renders - #504

Closed
hendrikheil wants to merge 1 commit into
nuxt-content:mainfrom
hendrikheil:fix/stable-async-component-identity
Closed

fix(MDCRenderer): keep async component identity stable across renders#504
hendrikheil wants to merge 1 commit into
nuxt-content:mainfrom
hendrikheil:fix/stable-async-component-identity

Conversation

@hendrikheil

Copy link
Copy Markdown
Contributor

🔗 Linked issue

Ref nuxt-content/nuxt-studio#383

📚 Description

defineAsyncComponent returns a new component type on every call, and resolveComponentInstance calls it from inside the render path:

if ('setup' in _component) {
  return defineAsyncComponent(() => new Promise(resolve => resolve(_component)))
}

resolveComponentInstance is passed straight into _renderSlots as resolveComponent and invoked per node on every render, so any component reaching this branch gets a different type on each pass. Vue can't patch across a type change — it unmounts and remounts the node, and the async wrapper renders nothing for a tick while it re-resolves.

Caching the wrapper in a WeakMap keyed by the resolved component keeps the type identity stable, so repeated renders patch instead of remount. Sharing one wrapper across usages is what a plain component import already does — the wrapper holds no per-instance state.

In practice most Nuxt setups take the AsyncComponentWrapper early-return above this branch and never reach it, so this is mostly latent there; it's more likely to bite standalone MDC users. The companion fix for the path that does fire under Nuxt Content is nuxt/content#3835.

Found while tracking down nuxt-content/nuxt-studio#383, where Studio's live preview remounts the whole rendered document on every keystroke.

📝 Checklist

  • I have linked an issue or discussion.
  • I have added tests (none — component-identity fix with no observable output change; existing suite passes, 63/63).

defineAsyncComponent returns a new component type on every call, and resolveComponentInstance runs from the render function. Vue sees a different type at the same vnode position and remounts the subtree instead of patching it.

Cache the wrappers so repeated resolutions return the same instance.

Ref nuxt-content/nuxt-studio#383
@pkg-pr-new

pkg-pr-new Bot commented Aug 18, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@nuxtjs/mdc@504

commit: ac1d290

@hendrikheil

Copy link
Copy Markdown
Contributor Author

Actually, I think this may not be reachable in practice, so I'll close this for now and rely on nuxt/content#3835 for now

@hendrikheil
hendrikheil deleted the fix/stable-async-component-identity branch August 18, 2026 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant