metaInfo becomes useHead/useSeoMetavue-meta was the standard for Vue 2 head management but never shipped a stable Vue 3 version. The repository was archived in October 2025 after years without updates. Unhead is its modern replacement—built by the same ecosystem, actively maintained, and Vue 3 native.
// vue-meta (Vue 2)
export default {
metaInfo() {
return {
title: 'My Page',
meta: [
{ name: 'description', content: 'Page description' }
]
}
}
}
// Unhead (Vue 3)
useSeoMeta({
title: 'My Page',
description: 'Page description'
})
Unhead's useSeoMeta() flattens the nested structure. No more meta arrays with name/content objects.
| vue-meta | Unhead | Notes |
|---|---|---|
metaInfo: {} | useHead({}) | Static object |
metaInfo() | useHead({}) with refs | Reactive by default |
title | title | Same |
titleTemplate: '%s - Site' | titleTemplate: '%s - Site' | Same |
meta: [{ name, content }] | useSeoMeta({ name: value }) | Flattened |
vmid / hid | key | For deduplication |
children | innerHTML | Script content |
body: true | tagPosition: 'bodyClose' | Script positioning |
npm uninstall vue-meta
npm install @unhead/vue
import Vue from 'vue'
import VueMeta from 'vue-meta'
Vue.use(VueMeta)
import { createHead } from '@unhead/vue/client'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
const head = createHead()
app.use(head)
app.mount('#app')
For SSR apps, import from @unhead/vue/server instead:
import { createHead } from '@unhead/vue/server'
<script>
export default {
data() {
return { pageTitle: 'About Us' }
},
metaInfo() {
return {
title: this.pageTitle,
titleTemplate: '%s | MySite',
meta: [
{ name: 'description', content: 'About our company' },
{ property: 'og:title', content: this.pageTitle },
{ property: 'og:description', content: 'About our company' }
]
}
}
}
</script>
<script setup lang="ts">
const pageTitle = ref('About Us')
useHead({
title: pageTitle,
titleTemplate: '%s | MySite'
})
useSeoMeta({
description: 'About our company',
ogTitle: pageTitle,
ogDescription: 'About our company'
})
</script>
| Find | Replace |
|---|---|
metaInfo() or metaInfo: { | useHead({ or useSeoMeta({ |
this.$meta().refresh() | Remove (automatic) |
vmid: | key: |
hid: | key: |
{ name: 'description', content: | description: (in useSeoMeta) |
{ property: 'og:title', content: | ogTitle: (in useSeoMeta) |
vue-meta required metaInfo() as a function for reactivity. Unhead is reactive by default—pass refs directly:
// vue-meta: function required for reactivity
export default {
metaInfo() {
return { title: this.dynamicTitle }
}
}
// Unhead: refs work automatically
const dynamicTitle = ref('Loading...')
useHead({ title: dynamicTitle })
Unhead v2 removed implicit context. Don't call useHead() after await without saving the head instance:
// ❌ May fail in Unhead v2
async function loadData() {
const data = await fetchData()
useHead({ title: data.title }) // Context lost
}
// ✅ Call before await or use injectHead()
const head = injectHead()
async function loadData() {
const data = await fetchData()
head.push({ title: data.title })
}
vue-meta had inject() for SSR. Unhead uses renderSSRHead():
// Server entry
import { renderSSRHead } from '@unhead/ssr'
const { headTags, bodyTags, bodyTagsOpen, htmlAttrs, bodyAttrs } = await renderSSRHead(head)
titleTemplate params like %separator require explicit plugin registration in Unhead v2:
import { createHead } from '@unhead/vue/client'
import { TemplateParamsPlugin } from '@unhead/vue/plugins'
const head = createHead({
plugins: [TemplateParamsPlugin]
})
| vue-meta | Unhead |
|---|---|
| Last stable: 2021 | Actively maintained |
| Vue 2 only | Vue 3 native |
| 30KB+ | ~8KB gzipped |
| Limited TypeScript | Full type safety |
| Community abandoned | Official Nuxt ecosystem |
Unhead also provides useSeoMeta() with autocomplete for all SEO properties—no more guessing meta tag names.
If you're using Nuxt, you don't need to install Unhead separately. It's built in. See Nuxt SEO for Nuxt-specific setup.