Get Started

Installing Unhead with Vue

Last updated by Harlan Wilton in docs: add SPA guide (#909).
On this page

Install @unhead/vue, create a head with createHead(), and register it through app.use(head). SSR uses separate client and server entry points with transformHtmlTemplate().

Unhead's Vue adapter originated in the @vueuse/head repository, which provided a Vue 3 successor to Vue Meta.

The examples follow Vite's SSR structure. A Vue SPA uses the same client entry.

Using Nuxt? Unhead is already integrated, and you can skip this guide.

Demos

Setup

1. Add Dependency

Install the @unhead/vue dependency in your project.

bash
pnpm add @unhead/vue@next

Vue 2

Unhead v2 and later do not support Vue 2. Vue 2 projects need @unhead/vue@^1.

bash
pnpm add @unhead/vue@^1

Then follow the v1 Vue installation guide.

Migrating from @vueuse/head

Follow the VueUse Head migration guide to remove the old package and update your imports.

2. Set Up Client-Side Rendering

Create the browser instance from @unhead/vue/client. In a Vite SSR app this usually belongs in entry-client.ts; in an SPA, put it in the main entry.

src/entry-client.ts
import './style.css'
import { createApp } from './main'
import { createHead } from '@unhead/vue/client'

const { app } = createApp()
const head = createHead()
app.use(head)

app.mount('#app')

3. Set Up Server-Side Rendering

Serving your app as an SPA? You can skip this step. See the SPA guide for template defaults, crawler limits, and route prerendering.

For SSR, create a fresh instance from @unhead/vue/server for each request and return it with the rendered app.

src/entry-server.ts
import { createHead } from '@unhead/vue/server'
import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'

export async function render(_url: string) {
  const { app } = createApp()
  const head = createHead()
  app.use(head)

  const ctx = {}
  const html = await renderToString(app, ctx)

  return { html, head }
}

After Vue renders, pass the instance and complete HTML template to transformHtmlTemplate():

server.ts
import { transformHtmlTemplate } from '@unhead/vue/server'
// ...

// Serve HTML
app.use('*all', async (req, res) => {
  try {
    // ...

    const rendered = await render(url)

    const html = transformHtmlTemplate(
      rendered.head,
      template.replace(`<!--app-html-->`, rendered.html ?? '')
    )

    res.status(200).set({ 'Content-Type': 'text/html' }).send(html)
  }
  catch (e) {
    // ...
  }
})
// ..

4. Server Defaults

On the server, Unhead inserts these default tags:

  • <meta charset="utf-8">
  • <meta name="viewport" content="width=device-width, initial-scale=1">
  • <html lang="en">

Set server-only defaults through init. Keeping them in the server entry also keeps them out of the client bundle.

src/entry-server.ts
import { createHead } from '@unhead/vue/server'

export async function render(_url: string) {
  // ...
  const head = createHead({
    init: [
      // change default initial lang
      {
        title: 'Default title',
        titleTemplate: '%s | My Site',
        htmlAttrs: { lang: 'fr' }
      },
    ]
  })
  // ...
}

bodyAttrs can set attributes or inline styles on <body>:

src/App.vue
<script setup lang="ts">
import { useHead } from '@unhead/vue'

useHead({
  bodyAttrs: {
    style: 'background: salmon; color: cyan;'
  },
})
</script>

5. Enable Auto-Imports

unplugin-auto-import can provide the composables without explicit imports:

vite.config.ts
import { unheadVueComposablesImports } from '@unhead/vue'
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    AutoImport({
      imports: [
        unheadVueComposablesImports,
      ],
    }),
    // ...
  ]
})

Next Steps

Composables:

Optional integrations:

Did this page help you?