Fix: Missing Unhead Instance in Nuxt

Error message:
[nuxt] [unhead] Missing Unhead instance.
head 2025-01-25

What Causes This Error?

This error occurs when you try to use head management functions (useHead, useSeoMeta, etc.) before Unhead has been initialized, or outside of a valid Nuxt context.

Common Causes

  1. Calling head composables outside of setup
  2. Using head functions in non-Nuxt contexts
  3. Plugin initialization order issues
  4. SSR context problems

The Fix

Ensure Head Composables Are in Setup

<script setup>
// ✅ Correct - called in script setup
useHead({
  title: 'My Page'
})

useSeoMeta({
  description: 'Page description'
})
</script>

Wrong: Outside Setup Context

// ❌ Wrong - outside of component
function updateTitle(title) {
  useHead({ title })  // Error!
}

// ✅ Correct - pass the composable result
function usePageMeta() {
  const head = useHead({
    title: 'Default'
  })

  function updateTitle(title) {
    head.patch({ title })
  }

  return { updateTitle }
}

Plugin Context

If using in a plugin, ensure proper context:

// ❌ Wrong - may run before Unhead is ready
export default defineNuxtPlugin(() => {
  useHead({ title: 'App' })  // May fail
})

// ✅ Correct - use nuxtApp.runWithContext
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('app:created', () => {
    nuxtApp.runWithContext(() => {
      useHead({ title: 'App' })
    })
  })
})

Dynamic Head Updates

For dynamic updates after setup:

<script setup>
// Create the head reference in setup
const head = useHead({
  title: 'Initial Title'
})

// Update later via patch
function updateTitle(newTitle) {
  head.patch({
    title: newTitle
  })
}

// Or use computed for reactive updates
const pageTitle = ref('My Page')
useHead({
  title: computed(() => pageTitle.value)
})
</script>

In Composables

// composables/usePageSeo.ts
export function usePageSeo(options: { title: string; description: string }) {
  // ✅ Called within composable that's used in setup
  useHead({
    title: options.title
  })

  useSeoMeta({
    description: options.description,
    ogTitle: options.title,
    ogDescription: options.description
  })
}

// Usage in component
<script setup>
usePageSeo({
  title: 'Products',
  description: 'Browse our products'
})
</script>

Middleware Context

Head composables work in middleware:

// middleware/set-title.ts
export default defineNuxtRouteMiddleware((to) => {
  // ✅ Works in middleware
  useHead({
    title: to.meta.title as string || 'Default'
  })
})

Server Routes (API)

For API routes, use the event-based API:

// server/api/og-image.ts
export default defineEventHandler((event) => {
  // ❌ Wrong - useHead doesn't work in server routes
  useHead({ title: 'API' })

  // Server routes don't render HTML,
  // so head management doesn't apply
})

Check Nuxt Context

Verify you’re in a valid context:

// Verify Nuxt is available
const nuxtApp = useNuxtApp()
if (nuxtApp) {
  useHead({ title: 'Safe' })
}

Multiple Unhead Instances

If you have conflicting Unhead setups:

// nuxt.config.ts
export default defineNuxtConfig({
  // Ensure only one Unhead instance
  experimental: {
    headNext: true  // Use latest head handling
  }
})

Debugging

Check if Unhead is initialized:

// In a component
const nuxtApp = useNuxtApp()
console.log('Unhead available:', !!nuxtApp.vueApp._context.provides.usehead)

Alternative: definePageMeta

For static head data, use definePageMeta:

<script setup>
// ✅ Alternative for static meta
definePageMeta({
  title: 'My Page',
  description: 'Page description'
})
</script>

Then in layout/app:

<script setup>
const route = useRoute()
useHead({
  title: computed(() => route.meta.title as string)
})
</script>

Quick Checklist

  • Head composables are called in <script setup> or setup function
  • Not called in callbacks/timers (use head.patch instead)
  • Plugin hooks properly await Unhead initialization
  • Not using head composables in server API routes
  • Check for multiple conflicting Unhead instances
  • Use computed for reactive head values