What Causes This Warning?
This warning occurs when you try to register a custom directive with the same name more than once using app.directive(). Vue prevents duplicate directive registrations to avoid confusion.
The Problem
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// ❌ Registering the same directive twice
app.directive('focus', {
mounted: (el) => el.focus()
})
app.directive('focus', {
mounted: (el) => el.focus()
}) // Warning: Directive "focus" already registered
app.mount('#app')
The Fix
Remove Duplicate Registration
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// ✅ Register once
app.directive('focus', {
mounted: (el) => el.focus()
})
app.mount('#app')
Centralize Directive Registration
// directives/index.js
export const focus = {
mounted: (el) => el.focus()
}
export const tooltip = {
mounted: (el, binding) => {
el.title = binding.value
}
}
export function registerDirectives(app) {
app.directive('focus', focus)
app.directive('tooltip', tooltip)
}
// main.js
import { registerDirectives } from './directives'
const app = createApp(App)
registerDirectives(app)
app.mount('#app')
Common Scenarios
Multiple Plugin Registrations
// ❌ Plugin registers directive, then registered again
// myPlugin.js
export default {
install(app) {
app.directive('highlight', { /* ... */ })
}
}
// main.js
app.use(MyPlugin)
app.directive('highlight', { /* ... */ }) // Duplicate!
// ✅ Let plugin handle registration
app.use(MyPlugin)
// Don't register again
Conditional Registration
// ❌ Might register twice
if (someCondition) {
app.directive('custom', directive1)
}
app.directive('custom', directive2) // Always runs!
// ✅ Clear conditional logic
if (someCondition) {
app.directive('custom', directive1)
} else {
app.directive('custom', directive2)
}
Library Integration
// Some libraries register their own directives
// Check documentation before adding your own
// ❌ Library might already register v-click-outside
import ClickOutside from 'some-library'
app.use(ClickOutside)
app.directive('click-outside', myClickOutside) // Conflict!
// ✅ Use library's directive or rename yours
app.use(ClickOutside) // Uses library's v-click-outside
app.directive('my-click-outside', myClickOutside) // Different name
Safe Registration Pattern
// directives/register.js
const registeredDirectives = new Set()
export function safeRegisterDirective(app, name, directive) {
if (registeredDirectives.has(name)) {
console.warn(`Directive "${name}" already registered, skipping`)
return
}
app.directive(name, directive)
registeredDirectives.add(name)
}
// Usage
safeRegisterDirective(app, 'focus', focusDirective)
safeRegisterDirective(app, 'focus', focusDirective) // Safely ignored
Local vs Global Directives
<!-- Local directive - no global conflict -->
<script setup>
// ✅ Define locally in component
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
// Global registration
app.directive('focus', {
mounted: (el) => el.focus()
})
// ❌ Same name locally will shadow global
// This is allowed but can cause confusion
Creating Reusable Directive Plugins
// directives/focusPlugin.js
const INSTALLED = Symbol('focus-directive')
export const FocusPlugin = {
install(app) {
// Prevent duplicate installation
if (app[INSTALLED]) return
app[INSTALLED] = true
app.directive('focus', {
mounted: (el, binding) => {
if (binding.value !== false) {
el.focus()
}
}
})
}
}
// Usage
app.use(FocusPlugin)
app.use(FocusPlugin) // Safely ignored
Common Directives to Watch
// These are common directive names that might conflict:
// v-focus, v-click-outside, v-tooltip, v-scroll
// v-lazy, v-loading, v-visible, v-resize
// Always check if a library provides these before defining your own
Quick Checklist
- Check for duplicate
app.directive()calls - Centralize directive registration
- Check library documentation for included directives
- Use unique names for custom directives
- Consider local directives for component-specific needs
- Add installation guards to directive plugins