Environment
Vuetify Version: 4.0.4
Vue Version: 3.5.31+
OS: Any (server-side rendering context)
Steps to reproduce
- Use Nuxt (or any SSR framework) with Vuetify 4.0.4 and Vue 3.5.31+
- Render any component using
useFocusTrap (e.g. VDialog, VMenu with retainFocus, VSelect)
- Request the page server-side
Expected Behavior
SSR rendering completes without errors.
Actual Behavior
SSR crashes with:
Error: Cannot read properties of undefined (reading 'removeEventListener')
at Array.<anonymous> (node_modules/vuetify/lib/composables/focusTrap.js:129:14)
at EffectScope.stop (@vue/reactivity/dist/reactivity.cjs.js:124:25)
at cleanupContext (@vue/server-renderer/dist/server-renderer.cjs.js:662:15)
at renderToString (@vue/server-renderer/dist/server-renderer.cjs.js:1014:5)
Root cause
Vue 3.5.31 fixed a bug where EffectScope.stop() was not executing onScopeDispose callbacks during SSR (vue-core#14548). With the fix, Vue now correctly calls those callbacks on the server — but Vuetify's onScopeDispose in composables/focusTrap.js calls document.removeEventListener directly without an IN_BROWSER guard:
if (IN_BROWSER) {
// ✅ setup listeners correctly guarded
}
onScopeDispose(() => {
registry.delete(trapId);
clearTimeout(focusTrapSuppressionTimeout);
document.removeEventListener('pointerdown', onPointerdown); // ❌ not guarded
document.removeEventListener('focusin', captureOnFocus);
document.removeEventListener('keydown', captureOnKeydown);
if (--subscribers < 1) {
document.removeEventListener('keydown', onKeydown);
}
});
On the server, document is undefined, causing the crash.
Workaround: pin Vue to 3.5.30 until a fix is released.
Expected fix: wrap the document.removeEventListener calls inside onScopeDispose with the same IN_BROWSER check used in the setup block.
Environment
Vuetify Version: 4.0.4
Vue Version: 3.5.31+
OS: Any (server-side rendering context)
Steps to reproduce
useFocusTrap(e.g.VDialog,VMenuwithretainFocus,VSelect)Expected Behavior
SSR rendering completes without errors.
Actual Behavior
SSR crashes with:
Root cause
Vue 3.5.31 fixed a bug where
EffectScope.stop()was not executingonScopeDisposecallbacks during SSR (vue-core#14548). With the fix, Vue now correctly calls those callbacks on the server — but Vuetify'sonScopeDisposeincomposables/focusTrap.jscallsdocument.removeEventListenerdirectly without anIN_BROWSERguard:On the server,
documentisundefined, causing the crash.Workaround: pin Vue to
3.5.30until a fix is released.Expected fix: wrap the
document.removeEventListenercalls insideonScopeDisposewith the sameIN_BROWSERcheck used in the setup block.