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
Nuxt 5 disables Vue’s Options API by default
Refernce: nuxt/nuxt#35791
For the time being, it can be enabled in Nuxt with:
What
app.mixin()captures todayvueInit()inpackages/vue/src/integration.tsregisters one mixin when tracing is on. That mixin produces every Vue UI span we emit:Application Renderui.rendertimeoutdebounce (2000 ms) ends itVue <Root>ui.mountmountbeforeMount/mountedVue <ComponentName>ui.mountmountbeforeMount/mountedtrackComponents)Vue <ComponentName>ui.mountactivateactivated/deactivatedtrackComponents)Vue <ComponentName>ui.mountcreatebeforeCreate/createdtrackComponents+hooks: ['create'])Vue <ComponentName>ui.updateupdatebeforeUpdate/updatedtrackComponents+hooks: ['update'])Vue <ComponentName>ui.unmountunmount(Vue 3)beforeUnmount/unmountedtrackComponents+hooks: ['unmount'])Vue <ComponentName>ui.unmountdestroy(Vue 2)beforeDestroy/destroyedtrackComponents+hooks: ['destroy'])Not affected: Pageload and navigation transactions come from
browserTracingIntegrationand the router. Error capture usesapp.config.errorHandler, which Vue does not gate. Pinia and TanStack Router never touch mixins.The alternative: register Composition API hooks in
setupComposition API lifecycle hooks are not gated by
__VUE_OPTIONS_API__. They write toinstance.m,instance.u, and so on, and the renderer invokes those arrays unconditionally. The question is only how to reach every component'ssetup.Two mechanisms, and the split runs between the root component and everything else.
Runtime, for the root.
createAppstores the root component asapp._component, a plain writable property, and shallow-copies it first, so mutatingapp._component.setupinvueInit()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 asetupto each.vuemodule's default export that registers the hooks and then calls the originalsetup. Wrap by adding to the existing options object rather than returning a new component, sonameand__filesurvive.mount(default on)onBeforeMount/onMountedApplication RenderandVue <Root>, identical to the mixin.vuethe plugin transformsactivate(default on)onActivated/onDeactivated<KeepAlive>.vuethe plugin transformsupdate(opt-in)onBeforeUpdate/onUpdated.vuethe plugin transformsunmount(opt-in)onBeforeUnmount/onUnmountedapp.unmount()only.vuethe plugin transformscreate(opt-in)setupis the create phasesetupComponent names stay correct.
compiler-sfcemits__namein production builds, andformatComponentNamealready 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 transformsnode_modules.Nuxt can get the plugin through the existing
addVitePlugincall inpackages/nuxt/src/module.ts. Plain@sentry/vueusers need a new install step.Vue 2 keeps the mixin
We cannot wrap
setupat 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 asapp._component. In Vue 2 the integration receives theVueconstructor, the user callsnew Vue({...})afterwards, and that options object never reaches us. Vue 2.7 backported the Composition API but explicitly did not portcreateApp, 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__isfalse, and Vue only warns in dev.We can detect if mixins are disabled and we warn the users: #23568