---
title: "useHead()"
description: "Manage document head tags with useHead(). Set titles, meta tags, scripts, and styles with full TypeScript support and reactive updates."
canonical_url: "https://unhead.unjs.io/docs/head/api/composables/use-head"
last_updated: "2026-05-09T18:27:36.156Z"
---

**Quick Start:**

```ts
import { useHead } from '@unhead/vue' // or your framework

useHead({
  title: 'Page Title',
  meta: [{ name: 'description', content: 'Page description' }]
})
```

The `useHead()` composable is for managing the document head. It provides a type-safe, reactive API to define, update, and remove head elements like title, meta tags, scripts, and more. It's the core composable used across all frameworks in the Unhead ecosystem.

```ts
import { useHead } from '@unhead/dynamic-import'

const entry = useHead({
  title: 'My Page',
})
// update
entry.patch({ title: 'new Title' })
// remove
entry.dispose()
```

### How It Works

The composable works by queuing your input to be resolved when the head is rendered:

1. It registers your head configuration in a queue
2. When the document head is being rendered (client-side or during SSR), all queued entries are:

  - Resolved (including any functions, promises or reactive values)
  - Deduplicated (removing redundant tags) - see [Handling Duplicates](/docs/head/guides/core-concepts/handling-duplicates)
  - Sorted (based on tag priority) - see [Tag Positions](/docs/head/guides/core-concepts/positions)
  - Merged when appropriate
3. The resolved tags are then rendered to the document head

This queue-based approach enables powerful features like deduplication, async resolution, and priority-based rendering while maintaining optimal performance.

<note>

You won't know the final state of the head until the rendering is complete.

</note>

### Reactivity Model

`useHead()` provides reactivity through two main mechanisms:

1. **Framework Integration**: When used with frameworks it automatically integrates with the framework's reactivity system
2. **Manual API**: The returned `ActiveHeadEntry` object with `patch()` and `dispose()` methods lets you manually update or remove head entries

## API Reference

```ts
function useHead(input: UseHeadInput, options?: HeadEntryOptions): ActiveHeadEntry
```

### Parameters

<table>
<thead>
  <tr>
    <th>
      Parameter
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        input
      </code>
    </td>
    
    <td>
      <code>
        Head
      </code>
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      The head configuration object
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        options
      </code>
    </td>
    
    <td>
      <code>
        HeadEntryOptions
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Configuration options for the head entry
    </td>
  </tr>
</tbody>
</table>

### Returns

```ts
interface ActiveHeadEntry {
  /**
   * Update the head entry with new values
   */
  patch: (input: Partial<UseHeadInput>) => void
  /**
   * Remove the head entry
   */
  dispose: () => void
}
```

## Input Schema

The input object accepts the following properties:

```ts
interface Head<E extends MergeHead = SchemaAugmentations> {
  // Document title
  title?: string | Promise<string>

  // Title template (function or string with %s placeholder)
  titleTemplate?: string | null | ((title?: string) => string | null)

  // Template parameters for dynamic replacements
  templateParams?: { separator?: string } & Record<string, string | Record<string, string>>

  // HTML tag collections
  base?: Base<E['base']>
  link?: Link<E['link']>[]
  meta?: Meta<E['meta']>[]
  style?: (Style<E['style']> | string)[]
  script?: (Script<E['script']> | string)[]
  noscript?: (Noscript<E['noscript']> | string)[]

  // Element attributes
  htmlAttrs?: HtmlAttributes<E['htmlAttrs']>
  bodyAttrs?: BodyAttributes<E['bodyAttrs']>
}
```

The input is deeply resolved allowing you to provide any value as a function. This can be useful
for lazily resolving values when the head tags are being rendered.

<tip>

Lazy resolving values can improve performance for complex or computed values that aren't needed until the head is actually rendered.

</tip>

```ts
import { useHead } from '@unhead/dynamic-import'

const title = useMyTitle()
useHead({
  // just works
  title: () => 'Dynamic Title',
  meta: [
    () => ({
      name: 'description',
      content: () => `Description for ${title.value}`
    }),
  ]
})
```

## Options

The `options` parameter allows you to configure the behavior of the head entry:

```ts
export interface HeadEntryOptions {
  // Whether to process template parameters in the input
  // - Requires the TemplateParams plugin
  processTemplateParams?: boolean

  // Priority of tags for determining render order
  tagPriority?: number | 'critical' | 'high' | 'low' | `before:${string}` | `after:${string}`

  // Where to position tags in the document
  tagPosition?: 'head' | 'bodyClose' | 'bodyOpen'

  // Callback fired after DOM updates are applied (client-only, ignored during SSR)
  onRendered?: (ctx: { renders: DomRenderTagContext[] }) => void | Promise<void>

  // Custom head instance
  head?: Unhead
}
```

Setting any of these will apply that rule to all tags within the entry. For example if we want to push several meta tags
with low priority, we can do:

<tip>

Learn more about using [Tag Priorities](/docs/head/guides/core-concepts/positions) and [Template Parameters](/docs/head/guides/plugins/template-params) in their dedicated guides.

</tip>

```ts
import { useHead } from '@unhead/dynamic-import'

useHead({
  meta: [
    { name: 'description', content: 'fallback description' },
    { name: 'author', content: 'fallback author' }
  ]
}, {
  tagPriority: 'low'
})
```

<note>

The `tagPriority` option is particularly useful for controlling render order when you have multiple head entries that might contain similar tags.

</note>

## Synchronizing with DOM Updates

The `onRendered` option lets you run a callback after unhead has finished applying DOM updates.
This is useful for synchronising external tools like analytics with the current document head (e.g. reading `document.title`
after it has been set).

```ts
import { useHead } from '@unhead/dynamic-import'

useHead({
  title: 'My Page',
}, {
  onRendered({ renders }) {
    // document.title is guaranteed to be up-to-date here
    analytics.track('Page View', { title: document.title })
  }
})
```

- The callback fires on **every** DOM render, not just the first.
- It is **ignored during SSR** — only runs on the client.
- The callback is automatically cleaned up when the entry is disposed (including on component unmount in frameworks).
- The `renders` array contains the render context for each tag that was processed.

Works with all composables that accept `HeadEntryOptions` — `useHead()`, `useSeoMeta()`, and `useHeadSafe()`.

## Reactivity

### Automatic Reactivity

The `useHead()` composable automatically integrates with your framework's reactivity system:

<framework-code>
<template v-slot:vue="">

```ts
import { useHead } from '@unhead/dynamic-import'
import { computed, ref } from 'vue'

const title = ref('Dynamic Title')

useHead({
  title,
  meta: [
    { name: 'description', content: computed(() => `Description for ${title.value}`) }
  ]
})
```

</template>

<template v-slot:react="">

```tsx
import { useHead } from '@unhead/dynamic-import'
import { useState } from 'react'

function MyPage() {
  const [title, setTitle] = useState('Dynamic Title')

  useHead({
    title: () => title,
    meta: [
      { name: 'description', content: () => `Description for ${title}` }
    ]
  })

  return <div>My Page</div>
}
```

</template>

<template v-slot:solid="">

```tsx
import { useHead } from '@unhead/dynamic-import'
import { createSignal } from 'solid-js'

function MyPage() {
  const [title, setTitle] = createSignal('Dynamic Title')

  useHead({
    title: () => title(),
    meta: [
      { name: 'description', content: () => `Description for ${title()}` }
    ]
  })

  return <div>My Page</div>
}
```

</template>
</framework-code>

<vue-only>

Vue automatically:

- Tracks reactive data changes with `watchEffect`
- Resolves refs, computed props, and reactive objects
- Cleans up head entries on component unmount
- Handles special cases like keep-alive components

</vue-only>

### Manual Control

For more granular control, you can use the returned API:

```ts
import { useHead } from '@unhead/dynamic-import'

// Create the head entry
const headControl = useHead({
  title: 'Initial Title'
})

// Later update specific fields
headControl.patch({
  title: 'Updated Title',
  meta: [
    { name: 'description', content: 'New description' }
  ]
})

// Remove the entry entirely when needed
headControl.dispose()
```

**Use cases for manual control:**

- Updating head after asynchronous data loading
- Conditional changes based on user interactions
- Managing head from global state
- Creating temporary modifications

For framework-specific reactivity details, see the guides for each specific framework.

## Security Considerations

<warning>

The `useHead()` function applies minimal sanitization to improve developer experience.

**Do not** use this function with untrusted or third-party input. It cannot guarantee safety when handling unknown content.

</warning>

For XSS protection, either:

1. Sanitize your input before passing it to `useHead()`
2. Use the safer alternatives:

  - [useSeoMeta()](/docs/head/api/composables/use-seo-meta) for SEO metadata
  - [useHeadSafe()](/docs/head/api/composables/use-head-safe) for general head management

## TypeScript

Import types directly from your framework's package:

```ts
import type { ActiveHeadEntry, Head, HeadEntryOptions } from '@unhead/vue'

// Type your head input
const headConfig: Head = {
  title: 'My Page',
  meta: [{ name: 'description', content: 'Page description' }]
}

// Type your entry options
const options: HeadEntryOptions = {
  tagPriority: 'high'
}

const entry: ActiveHeadEntry = useHead(headConfig, options)
```

For custom type extensions, augment the `Head` interface:

```ts
declare module '@unhead/schema' {
  interface Head {
    customProperty?: string
  }
}
```

### Type Narrowing

`Link` and `Script` use discriminated unions keyed on `rel` and `type`. Known values enforce per-tag required properties at the type level. For non-standard values not covered by the built-in union, use the `defineLink` and `defineScript` helpers, which preserve strict narrowing on known values and fall through to `GenericLink` / `GenericScript` for anything else:

<code-group>

```ts [Non-standard Link rel]
import { defineLink, useHead } from '@unhead/dynamic-import'

useHead({
  link: [
    { rel: 'canonical', href: 'https://example.com' }, // known rel, works directly
    { rel: 'me', href: 'https://mastodon.social/@me' }, // known rel, works directly
    defineLink({ rel: 'openid2.provider', href: 'https://example.com/openid' }), // non-standard rel
  ]
})
```

```ts [Custom Script type]
import { defineScript, useHead } from '@unhead/dynamic-import'

useHead({
  script: [
    { src: 'https://example.com/app.js' }, // external script, works directly
    defineScript({ type: 'text/plain', textContent: '...' }), // custom type
  ]
})
```

</code-group>

<tip>

When building link or script objects outside of `useHead()`, use `as const` on literal values to preserve type narrowing:

```ts
const link = { rel: 'preload' as const, as: 'font' as const, href: '/font.woff2', crossorigin: 'anonymous' as const }
useHead({ link: [link] })
```

</tip>

<tip>

`useSeoMeta()` is unaffected by type narrowing and remains the simplest path for SEO meta tags.

</tip>

## Common Mistakes

### Using reactive values incorrectly

```ts
// ❌ Wrong - loses reactivity
const title = ref('My Title')
useHead({ title: title.value })

// ✅ Correct - pass the ref directly
useHead({ title })
```

### Calling useHead in async code

```ts
// ❌ Wrong - may execute outside component context
async function loadData() {
  const data = await fetchData()
  useHead({ title: data.title }) // Context may be lost
}

// ✅ Correct - set up head first, update reactively
const data = ref(null)
useHead({ title: () => data.value?.title ?? 'Loading...' })
async function loadData() {
  data.value = await fetchData()
}
```

### Forgetting to dispose manual entries

```ts
// ❌ Memory leak if called multiple times
function showModal() {
  useHead({ title: 'Modal Open' })
}

// ✅ Store and dispose when done
let modalHead: ActiveHeadEntry | null = null
function showModal() {
  modalHead = useHead({ title: 'Modal Open' })
}
function hideModal() {
  modalHead?.dispose()
  modalHead = null
}
```

## Choosing the Right Composable

<table>
<thead>
  <tr>
    <th>
      Composable
    </th>
    
    <th>
      Use When
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        useHead()
      </code>
    </td>
    
    <td>
      General head management, scripts, links, full control
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useSeoMeta()
      </code>
    </td>
    
    <td>
      SEO meta tags only - simpler API, type-safe keys
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useHeadSafe()
      </code>
    </td>
    
    <td>
      Working with untrusted/user-provided input
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useScript()
      </code>
    </td>
    
    <td>
      Loading third-party scripts with lifecycle control
    </td>
  </tr>
</tbody>
</table>

<tip>

Start with `useSeoMeta()` for SEO tags - it's simpler and prevents mistakes. Use `useHead()` when you need scripts, links, or advanced features.

</tip>

## Common Questions

### How do I update the title dynamically?

Use a reactive value or the `patch()` method:

```ts
const entry = useHead({ title: 'Initial' })
entry.patch({ title: 'Updated Title' })
```

### How do I remove head tags?

Call `dispose()` on the returned entry:

```ts
const entry = useHead({ title: 'Temporary' })
entry.dispose() // removes all tags from this entry
```

## Advanced Examples

### Title Template

```ts
import { useHead } from '@unhead/dynamic-import'

useHead({
  titleTemplate: title => `${title} - My Site`,
  title: 'Home Page'
})
// Results in: "Home Page - My Site"
```

For more details on title templates, see the [Titles guide](/docs/head/guides/core-concepts/titles).

### Combining Multiple Head Entries

```ts
import { useHead } from '@unhead/dynamic-import'

// Global site defaults
useHead({
  titleTemplate: '%s | My Website',
  meta: [
    { name: 'og:site_name', content: 'My Website' }
  ]
})

// Page-specific entries (will be merged with globals)
useHead({
  title: 'Product Page',
  meta: [
    { name: 'description', content: 'This product is amazing' }
  ]
})
```

<tip>

This pattern is commonly used to implement layouts with defaults and page-specific overrides.

</tip>

### Async Data Loading

<framework-code>
<template v-slot:vue="">

```ts
import { useHead } from '@unhead/dynamic-import'
import { computed, ref } from 'vue'

const data = ref(null)
const loading = ref(true)

useHead({
  title: computed(() => data.value
    ? `${data.value.name} - Product`
    : loading.value
      ? 'Loading...'
      : 'Product Not Found')
})

async function fetchProduct(id) {
  loading.value = true
  data.value = await api.getProduct(id)
  loading.value = false
}
```

</template>

<template v-slot:react="">

```tsx
import { useHead } from '@unhead/dynamic-import'
import { useState } from 'react'

function ProductPage({ id }) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)

  useHead({
    title: () => data
      ? `${data.name} - Product`
      : loading
        ? 'Loading...'
        : 'Product Not Found'
  })

  return <div>Product Page</div>
}
```

</template>

<template v-slot:solid="">

```tsx
import { useHead } from '@unhead/dynamic-import'
import { createSignal } from 'solid-js'

function ProductPage(props) {
  const [data, setData] = createSignal(null)
  const [loading, setLoading] = createSignal(true)

  useHead({
    title: () => data()
      ? `${data().name} - Product`
      : loading()
        ? 'Loading...'
        : 'Product Not Found'
  })

  return <div>Product Page</div>
}
```

</template>
</framework-code>

<note>

This pattern works well with data fetching libraries and state management solutions.

</note>

### Priority-Based Tag Ordering

```ts
import { useHead } from '@unhead/dynamic-import'

// Critical meta tags (early in <head>)
useHead({
  meta: [
    { charset: 'utf-8' },
    { name: 'viewport', content: 'width=device-width, initial-scale=1' }
  ]
}, { tagPriority: 'critical' })

// Default priority tags (middle of <head>)
useHead({
  meta: [
    { name: 'description', content: 'My website description' }
  ]
})

// Low priority tags (end of <head>)
useHead({
  meta: [
    { name: 'author', content: 'Jane Doe' }
  ]
}, { tagPriority: 'low' })
```

## Common Use Cases

Here are some common use cases for `useHead()`:

- Setting page-specific metadata for SEO (consider using [useSeoMeta()](/docs/head/api/composables/use-seo-meta) for a more convenient API)
- Managing document title and favicon (see [Titles guide](/docs/head/guides/core-concepts/titles))
- Adding external scripts and stylesheets (consider using [useScript()](/docs/head/api/composables/use-script) for scripts)
- Setting Open Graph and Twitter card tags

For ready-to-use implementations of common patterns, see our [Starter Recipes](/docs/head/guides/get-started/starter-recipes).
