What Causes This Error?
This error occurs when two or more plugins depend on each other in a circular way:
- Plugin A depends on Plugin B
- Plugin B depends on Plugin A
Nuxt can’t determine which to load first, causing an infinite loop.
The Problem
// plugins/a.ts
export default defineNuxtPlugin({
name: 'plugin-a',
dependsOn: ['plugin-b'], // A depends on B
setup() {
console.log('Plugin A')
}
})
// plugins/b.ts
export default defineNuxtPlugin({
name: 'plugin-b',
dependsOn: ['plugin-a'], // B depends on A - CIRCULAR!
setup() {
console.log('Plugin B')
}
})
How to Fix It
Option 1: Remove Unnecessary Dependency
Often, one of the dependencies isn’t actually needed:
// plugins/a.ts
export default defineNuxtPlugin({
name: 'plugin-a',
// Removed dependency on plugin-b
setup() {
console.log('Plugin A')
}
})
// plugins/b.ts
export default defineNuxtPlugin({
name: 'plugin-b',
dependsOn: ['plugin-a'], // Only B depends on A
setup() {
console.log('Plugin B')
}
})
Option 2: Extract Shared Logic
Create a third plugin with the shared functionality:
// plugins/shared.ts
export default defineNuxtPlugin({
name: 'plugin-shared',
setup() {
// Shared logic that both A and B need
return {
provide: {
sharedUtil: () => 'shared'
}
}
}
})
// plugins/a.ts
export default defineNuxtPlugin({
name: 'plugin-a',
dependsOn: ['plugin-shared'],
setup() {
const { $sharedUtil } = useNuxtApp()
// Use shared utility
}
})
// plugins/b.ts
export default defineNuxtPlugin({
name: 'plugin-b',
dependsOn: ['plugin-shared'],
setup() {
const { $sharedUtil } = useNuxtApp()
// Use shared utility
}
})
Option 3: Use Lazy Initialization
Instead of dependencies, use lazy access:
// plugins/a.ts
export default defineNuxtPlugin({
name: 'plugin-a',
setup(nuxtApp) {
return {
provide: {
a: {
getValue() {
// Lazily access plugin B when needed
return nuxtApp.$b?.someValue
}
}
}
}
}
})
// plugins/b.ts
export default defineNuxtPlugin({
name: 'plugin-b',
setup(nuxtApp) {
return {
provide: {
b: {
someValue: 42,
getValue() {
// Lazily access plugin A when needed
return nuxtApp.$a?.getValue()
}
}
}
}
}
})
Option 4: Merge Plugins
If plugins are tightly coupled, combine them:
// plugins/combined.ts
export default defineNuxtPlugin({
name: 'plugin-combined',
setup() {
// Logic from plugin A
const aFeature = () => 'A'
// Logic from plugin B
const bFeature = () => 'B'
// They can now reference each other freely
const combined = () => aFeature() + bFeature()
return {
provide: {
a: { feature: aFeature },
b: { feature: bFeature },
combined
}
}
}
})
Option 5: Use Events/Hooks
Decouple using events:
// plugins/a.ts
export default defineNuxtPlugin({
name: 'plugin-a',
setup(nuxtApp) {
// Listen for event from B
nuxtApp.hook('b:ready', (bData) => {
console.log('B is ready:', bData)
})
// Emit when A is ready
nuxtApp.callHook('a:ready', { value: 'A' })
}
})
// plugins/b.ts
export default defineNuxtPlugin({
name: 'plugin-b',
setup(nuxtApp) {
// Listen for event from A
nuxtApp.hook('a:ready', (aData) => {
console.log('A is ready:', aData)
})
// Emit when B is ready
nuxtApp.callHook('b:ready', { value: 'B' })
}
})
Debugging Circular Dependencies
Visualize the Dependency Graph
// In nuxt.config.ts
export default defineNuxtConfig({
hooks: {
'app:resolve'(app) {
console.log('Plugins:', app.plugins.map(p => ({
src: p.src,
name: p.name
})))
}
}
})
Check Plugin Load Order
// plugins/debug.ts
export default defineNuxtPlugin({
name: 'plugin-debug',
enforce: 'pre', // Load first
setup() {
console.log('Debug plugin loaded')
}
})
Plugin Ordering Without Dependencies
Use enforce to control order without explicit dependencies:
// plugins/first.ts
export default defineNuxtPlugin({
name: 'first',
enforce: 'pre', // Runs before default plugins
setup() {}
})
// plugins/middle.ts
export default defineNuxtPlugin({
name: 'middle',
// enforce: 'default' is implied
setup() {}
})
// plugins/last.ts
export default defineNuxtPlugin({
name: 'last',
enforce: 'post', // Runs after default plugins
setup() {}
})
Best Practices
- Minimize dependencies - Only depend on what you truly need
- Use composition - Create small, focused plugins
- Prefer loose coupling - Use events/hooks instead of direct dependencies
- Document dependencies - Make plugin relationships clear
- Test plugin order - Verify plugins load correctly
Quick Checklist
- Identify the circular dependency chain
- Determine which dependency can be removed
- Consider extracting shared logic to a base plugin
- Use lazy initialization for cross-plugin access
- Consider merging tightly coupled plugins
- Use
enforcefor ordering without dependencies