Repository navigation
[Nuxt 5] Replace the Vue integration’s reliance on app.mixin() #23375
Description
Nuxt 5 disables Vue’s Options API by default
Refernce: nuxt/nuxt#35791
For the time being, it can be enabled in Nuxt with:
export default defineNuxtConfig({
vue: {
optionsApi: true
}
})What app.mixin() captures today
vueInit() in packages/vue/src/integration.ts registers one mixin when tracing is on. That mixin produces every Vue UI span we emit:
| Span | Op | Operation | Mixin hooks (start / end) | On by default |
|---|---|---|---|---|
Application Render |
ui.render |
any | the root's first hook starts it, a timeout debounce (2000 ms) ends it |
yes |
Vue <Root> |
ui.mount |
mount |
beforeMount / mounted |
yes |
Vue <ComponentName> |
ui.mount |
mount |
beforeMount / mounted |
no (trackComponents) |
Vue <ComponentName> |
ui.mount |
activate |
activated / deactivated |
no (trackComponents) |
Vue <ComponentName> |
ui.mount |
create |
beforeCreate / created |
no (trackComponents + hooks: ['create']) |
Vue <ComponentName> |
ui.update |
update |
beforeUpdate / updated |
no (trackComponents + hooks: ['update']) |
Vue <ComponentName> |
ui.unmount |
unmount (Vue 3) |
beforeUnmount / unmounted |
no (trackComponents + hooks: ['unmount']) |
Vue <ComponentName> |
ui.unmount |
destroy (Vue 2) |
beforeDestroy / destroyed |
no (trackComponents + hooks: ['destroy']) |
Not affected: Pageload and navigation transactions come from browserTracingIntegration and the router. Error capture uses app.config.errorHandler, which Vue does not gate. Pinia and TanStack Router never touch mixins.
The alternative: register Composition API hooks in setup
Composition API lifecycle hooks are not gated by __VUE_OPTIONS_API__. They write to instance.m, instance.u, and so on, and the renderer invokes those arrays unconditionally. The question is only how to reach every component's setup.
Two mechanisms, and the split runs between the root component and everything else.
Runtime, for the root. createApp stores the root component as app._component, a plain writable property, and shallow-copies it first, so mutating app._component.setup in vueInit() does not touch the user's module. Function root components are not copied, so replace the property instead of mutating it. This covers both default-on spans with no plugin and no new install step.
Build time, for trackComponents. A Vite, Rollup, or webpack plugin adds a setup to each .vue module's default export that registers the hooks and then calls the original setup. Wrap by adding to the existing options object rather than returning a new component, so name and __file survive.
| Operation | Composition API hook | Runtime root wrap | Build-time plugin |
|---|---|---|---|
mount (default on) |
onBeforeMount / onMounted |
Application Render and Vue <Root>, identical to the mixin |
every .vue the plugin transforms |
activate (default on) |
onActivated / onDeactivated |
never fires, the root is not inside <KeepAlive> |
every .vue the plugin transforms |
update (opt-in) |
onBeforeUpdate / onUpdated |
root re-renders only | every .vue the plugin transforms |
unmount (opt-in) |
onBeforeUnmount / onUnmounted |
app.unmount() only |
every .vue the plugin transforms |
create (opt-in) |
none, setup is the create phase |
approximate, brackets the original setup |
approximate, same |
Component names stay correct. compiler-sfc emits __name in production builds, and formatComponentName already reads it.
What it misses: render function and (non SFC) JSX components, runtime compiled templates, CDN builds, and pre-compiled components in node_modules, including Nuxt's own <NuxtPage> and <NuxtLayout>, unless the plugin also transforms node_modules.
Nuxt can get the plugin through the existing addVitePlugin call in packages/nuxt/src/module.ts. Plain @sentry/vue users need a new install step.
Vue 2 keeps the mixin
We cannot wrap setup at runtime in Vue 2, and Vue 2.7 does not change that.
The runtime wrap needs a handle on the root component's options object before Vue instantiates it. In Vue 3, createApp(App) gives us exactly that as app._component. In Vue 2 the integration receives the Vue constructor, the user calls new Vue({...}) afterwards, and that options object never reaches us. Vue 2.7 backported the Composition API but explicitly did not port createApp, and 2.7 is the last Vue 2 minor.
Warning users on missing mixins
app.mixin() is a silent no-op when __VUE_OPTIONS_API__ is false, and Vue only warns in dev.
We can detect if mixins are disabled and we warn the users: #23568
Activity
linear-code commentedon Aug 12, 2026
JS-3365
test(vue): Add test cases for using Options API (mixins) (#23564)
feat(vue): Add Options API detection guard (#23568)
test(vue): Add test cases for using Options API (mixins) (#23564)
test(vue/e2e): Add tests for Vue UI spans (#24001)
feat(vue,nuxt): Record default UI spans without Options API (mixins) (#…
github-actions commentedon Sep 23, 2026
A PR closing this issue has just been released 🚀
This issue was referenced by PR #24174, which was included in the 11.0.0 release.
feat(v10/vue,nuxt): Record default UI spans without Options API (mixi…