--- title: useHead() description: Manage document head tags with useHead(). Set titles, meta tags, scripts, and styles with full TypeScript support and reactive updates. --- **Quick Start:** ```ts import { useHead } from '@unhead/vue' // or your framework useHead({ title: 'Page Title', meta: [{ name: 'description', content: 'Page description' }] }) ``` The `useHead()`{lang="ts"} 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. :: ### Reactivity Model `useHead()`{lang="ts"} 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 | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `input` | `Head` | Yes | The head configuration object | | `options` | `HeadEntryOptions` | No | Configuration options for the head entry | ### Returns ```ts interface ActiveHeadEntry { /** * Update the head entry with new values */ patch: (input: Partial) => void /** * Remove the head entry */ dispose: () => void } ``` ## Input Schema The input object accepts the following properties: ```ts interface Head { // Document title title?: string | Promise // Title template (function or string with %s placeholder) titleTemplate?: string | null | ((title?: string) => string | null) // Template parameters for dynamic replacements templateParams?: { separator?: string } & Record> // HTML tag collections base?: Base link?: Link[] meta?: Meta[] style?: (Style | string)[] script?: (Script | string)[] noscript?: (Noscript | string)[] // Element attributes htmlAttrs?: HtmlAttributes bodyAttrs?: BodyAttributes } ``` 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. :: ```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 // 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. :: ```ts import { useHead } from '@unhead/dynamic-import' useHead({ meta: [ { name: 'description', content: 'fallback description' }, { name: 'author', content: 'fallback author' } ] }, { tagPriority: 'low' }) ``` ::note The `tagPriority`{lang="ts"} option is particularly useful for controlling render order when you have multiple head entries that might contain similar tags. :: ## 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()`{lang="ts"} composable automatically integrates with your framework's reactivity system: ::FrameworkCode #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}`) } ] }) ``` #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
My Page
} ``` #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
My Page
} ``` :: ::VueOnly 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 :: ### 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()`{lang="ts"} 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. :: For XSS protection, either: 1. Sanitize your input before passing it to `useHead()`{lang="ts"} 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 ] }) ``` :: ::tip When building link or script objects outside of `useHead()`{lang="ts"}, 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 `useSeoMeta()`{lang="ts"} is unaffected by type narrowing and remains the simplest path for SEO meta tags. :: ## 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 | Composable | Use When | |------------|----------| | `useHead()` | General head management, scripts, links, full control | | `useSeoMeta()` | SEO meta tags only - simpler API, type-safe keys | | `useHeadSafe()` | Working with untrusted/user-provided input | | `useScript()` | Loading third-party scripts with lifecycle control | ::tip Start with `useSeoMeta()` for SEO tags - it's simpler and prevents mistakes. Use `useHead()` when you need scripts, links, or advanced features. :: ## 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. :: ### Async Data Loading ::FrameworkCode #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 } ``` #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
Product Page
} ``` #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
Product Page
} ``` :: ::note This pattern works well with data fetching libraries and state management solutions. :: ### Priority-Based Tag Ordering ```ts import { useHead } from '@unhead/dynamic-import' // Critical meta tags (early in ) useHead({ meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' } ] }, { tagPriority: 'critical' }) // Default priority tags (middle of ) useHead({ meta: [ { name: 'description', content: 'My website description' } ] }) // Low priority tags (end of ) useHead({ meta: [ { name: 'author', content: 'Jane Doe' } ] }, { tagPriority: 'low' }) ``` ## Common Use Cases Here are some common use cases for `useHead()`{lang="ts"}: - 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).