---
title: "Migrating from vue-meta to Unhead"
description: "Migrate from vue-meta to Unhead in Vue 3, with syntax mapping, breaking changes, and search-and-replace patterns."
canonical_url: "https://nuxtseo.com/learn-seo/vue/mastering-meta/migrating-vue-meta"
last_updated: "2026-07-16"
---

::key-takeaways
- vue-meta never shipped Vue 3 support and was archived in 2025
- Unhead is the modern replacement with full Vue 3 support
- Most APIs translate directly: `metaInfo` becomes `useHead`/`useSeoMeta`
::

vue-meta was the standard for Vue 2 head management but never shipped a stable Vue 3 version: it stalled at a `3.0.0-alpha` release. The [repository was archived](https://github.com/nuxt/vue-meta) in October 2025 after years without updates. [Unhead](https://unhead.unjs.io/) is its modern replacement, built by the same ecosystem and Vue 3 native from the start.

## Quick Comparison

::code-group
```ts [vue-meta (Vue 2)]
export default {
  metaInfo() {
    return {
      title: 'My Page',
      meta: [
        { name: 'description', content: 'Page description' }
      ]
    }
  }
}
```

```ts [Unhead (Vue 3)]
import { useSeoMeta } from '@unhead/vue'

useSeoMeta({
  title: 'My Page',
  description: 'Page description'
})
```
::

Unhead's `useSeoMeta()`{lang="ts"} flattens the nested structure. No more `meta` arrays with `name`/`content` objects.

## Syntax Mapping

| vue-meta                     | Unhead                                   | Notes               |
| ---------------------------- | ---------------------------------------- | ------------------- |
| `metaInfo: {}`               | `useHead({})`{lang="ts"}                 | Static object       |
| `metaInfo()`{lang="ts"}      | `useHead({})`{lang="ts"} with refs       | Reactive by default |
| `title`                      | `title`                                  | Same                |
| `titleTemplate: '%s - Site'` | `titleTemplate: '%s - Site'`             | Same                |
| `meta: [{ name, content }]`  | `useSeoMeta({ name: value })`{lang="ts"} | Flattened           |
| `vmid` / `hid`               | `key`                                    | For deduplication   |
| `children`                   | `innerHTML`                              | Script content      |
| `body: true`                 | `tagPosition: 'bodyClose'`               | Script positioning  |

## Migration Steps

### 1. Remove vue-meta

```bash
npm uninstall vue-meta
npm install @unhead/vue
```

### 2. Update Plugin Setup

::code-group
```ts [vue-meta (before)]
import Vue from 'vue'
import VueMeta from 'vue-meta'

Vue.use(VueMeta)
```

```ts [Unhead (after)]
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`{lang="ts"} instead:

```ts
import { createHead } from '@unhead/vue/server'
```

### 3. Convert Components

::code-group
```vue [vue-meta (before)]
<script lang="ts">
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>
```

```vue [Unhead (after)]
<script setup lang="ts">
import { useHead, useSeoMeta } from '@unhead/vue'
import { ref } from 'vue'

const pageTitle = ref('About Us')

useHead({
  title: pageTitle,
  titleTemplate: '%s | MySite'
})

useSeoMeta({
  description: 'About our company',
  ogTitle: pageTitle,
  ogDescription: 'About our company'
})
</script>
```
::

### 4. Search and Replace Patterns

| Find                                     | Replace                        |
| ---------------------------------------- | ------------------------------ |
| `metaInfo()`{lang="ts"} or `metaInfo: {` | `useHead({` or `useSeoMeta({`  |
| `this.$meta().refresh()`{lang="ts"}      | Remove (automatic)             |
| `vmid:`                                  | `key:`                         |
| `hid:`                                   | `key:`                         |
| `{ name: 'description', content:`        | `description:` (in useSeoMeta) |
| `{ property: 'og:title', content:`       | `ogTitle:` (in useSeoMeta)     |

## Breaking Changes

### Reactivity Model

vue-meta required `metaInfo()`{lang="ts"} as a function for reactivity; Unhead is reactive by default, so pass refs directly:

::code-group
```vue [❌ Bad]
<script lang="ts">
// vue-meta: function required for reactivity
export default {
  metaInfo() {
    return { title: this.dynamicTitle }
  }
}
</script>
```

```vue [✅ Good]
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { ref } from 'vue'

// Unhead: refs work automatically
const dynamicTitle = ref('Loading...')
useHead({ title: dynamicTitle })
</script>
```
::

### No Implicit Context After Async

Unhead v2 removed implicit context. Don't call `useHead()`{lang="ts"} after `await`{lang="ts"} without saving the head instance:

::code-group
```ts [❌ Bad]
import { useHead } from '@unhead/vue'

// May fail in Unhead v2
async function loadData() {
  const data = await fetchData()
  useHead({ title: data.title }) // Context lost
}
```

```ts [✅ Good]
import { injectHead } from '@unhead/vue'

// Call before await or use injectHead()
const head = injectHead()
async function loadData() {
  const data = await fetchData()
  head.push({ title: data.title })
}
```
::

### SSR Rendering

vue-meta had `inject()`{lang="ts"} for SSR. Unhead uses `renderSSRHead()`{lang="ts"}:

```ts
// Server entry
import { renderSSRHead } from '@unhead/ssr'

const { headTags, bodyTags, bodyTagsOpen, htmlAttrs, bodyAttrs } = await renderSSRHead(head)
```

### Template Params Plugin

`titleTemplate` params like `%separator` require explicit plugin registration in Unhead v2:

```ts
import { createHead } from '@unhead/vue/client'
import { TemplateParamsPlugin } from '@unhead/vue/plugins'

const head = createHead({
  plugins: [TemplateParamsPlugin()]
})
```

## Why Migrate?

| vue-meta                  | Unhead                  |
| ------------------------- | ----------------------- |
| Last stable release: 2020 | Actively maintained     |
| Vue 2 only                | Vue 3 native            |
| Limited TypeScript        | Full type safety        |
| Community abandoned       | Official Nuxt ecosystem |

Unhead also provides `useSeoMeta()`{lang="ts"} with autocomplete for all SEO properties, so there's 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](/docs/nuxt-seo/getting-started/introduction) for Nuxt-specific setup.

## Checklist

::checklist{#vue-meta-migration}
- Uninstalled `vue-meta` and installed `@unhead/vue`
- Replaced `Vue.use(VueMeta)`{lang="ts"} with `createHead()`{lang="ts"} and `app.use(head)`{lang="ts"}
- Converted every `metaInfo()`{lang="ts"} block to `useHead()`{lang="ts"} or `useSeoMeta()`{lang="ts"}
- Renamed `vmid`/`hid` fields to `key`
- Pass refs and computed getters directly to `useHead()`{lang="ts"}, not `.value`
- Call `useHead()`{lang="ts"} before an `await`, or use `injectHead()`{lang="ts"} to keep context after async code
- Registered `TemplateParamsPlugin()`{lang="ts"} if you use custom `titleTemplate` params like `%separator`
::