unjs/unhead @unhead/vue
Full-stack <head> manager built for Vue.
Version: 2.1.12 (Mar 2026) Deps: hookable@^6.0.1, unhead@2.1.12 Tags: next: 3.0.0-beta.9 (Feb 2026), beta: 3.0.0-beta.12 (Mar 2026), latest: 2.1.12 (Mar 2026)
References: Docs — API reference, guides
API Changes
This section documents version-specific API changes — prioritize recent major/minor releases.
-
BREAKING:
createHead()andcreateServerHead()removed from@unhead/vueroot in v2 — use subpath imports:createHead()from@unhead/vue/client(SPA) or@unhead/vue/server(SSR);createServerHead()no longer exists source -
BREAKING: Implicit context removed in v2 —
setHeadInjectionHandler()deleted;useHead()called after anawaitin lifecycle hooks (e.g.onMounted) throws because Vue context is lost; wrap async data fetching before callinguseHead()source -
BREAKING:
vmidandhidtag properties removed in v2 — usekeyfor deduplication:script: [{ key: 'my-key' }]source -
BREAKING:
childrentag property removed in v2 — useinnerHTMLinstead source -
BREAKING:
body: truetag property removed in v2 — usetagPosition: 'bodyClose'instead source -
BREAKING:
useScript()no longer returns a Promise in v2 —.then()calls silently fail; use.onLoaded(() => ...)instead source -
BREAKING:
useScript()API no longer accessible directly on the instance in v2 — must use.proxyexplicitly:script.proxy.myFn()notscript.myFn(); code compiles but calls are lost at runtime source -
BREAKING:
stub()option andscript:instance-fnhook removed fromuseScript()in v2 — replace with customuse()logic source -
BREAKING: Promise inputs in
useHead()no longer auto-resolved in v2 — await the promise before passing, or opt in toPromisePluginfrom@unhead/vue/pluginssource -
BREAKING:
TemplateParamsPluginandAliasSortingPluginno longer built-in in v2 — must opt in:createHead({ plugins: [TemplateParamsPlugin, AliasSortingPlugin] })imported from@unhead/vue/pluginssource -
BREAKING: Capo.js tag sorting is now the default in v2 — breaks snapshot tests; opt out with
createHead({ disableCapoSorting: true })source -
DEPRECATED:
useServerHead(),useServerHeadSafe(),useServerSeoMeta()— useuseHead(),useHeadSafe(),useSeoMeta()withimport.meta.serverconditionals or{ mode: 'server' }option for tree-shaking -
NEW:
<Head>,<Title>,<Meta>,<Link>,<Script>template components — import from@unhead/vue/componentssource -
NEW:
DeprecationsPluginfrom@unhead/vue/plugins— re-enables removedvmid,hid,children,bodyproperties for gradual migration to v2 source
Also changed: @unhead/schema deprecated — use @unhead/vue/types instead · createHeadCore deprecated — use createUnhead · Default SSR tags auto-inserted in v2 (charset, viewport, html lang="en"); disable with createHead({ disableDefaults: true }) · CJS exports removed, ESM only · Vue 2 support removed · useHead() context lost after async in Vue lifecycle hooks — fetch data first, then call useHead()
Best Practices
-
Always use
injectHead()from@unhead/vueinstead ofgetActiveHead()fromunheadin Vue components —injectHead()binds to the Vue component context (visible inonServerPrefetch), whilegetActiveHead()returns a shared cross-request instance that breaks in SSR. The maintainer confirmed this is the correct approach for Vue. source -
Avoid calling
useHead()inside watchers — each call creates a new entry rather than updating the existing one, leading to duplicate entries. Instead, pass reactive refs or computed getters directly to a singleuseHead()call at setup time so updates flow automatically. source -
When
useHead()must be called after async operations (e.g. insideonMounted), capture the head instance at setup time withinjectHead()and pass it as{ head }in the second argument — Vue's inject context is lost afterawait. For most cases, prefer the reactive state pattern: defineuseHead()once at setup with computed getters, and update arefasynchronously. source -
Use
useHeadSafe()instead ofuseHead()whenever head input comes from user-provided or third-party sources — it enforces an attribute whitelist and strips script tags and event handlers, preventing XSS without requiring manual sanitization. source -
Add the
UnheadVite()plugin from@unhead/addons/viteto your Vite config for Vue apps — it tree-shakes server-only composables from the client build and transformsuseSeoMeta()calls into rawuseHead()calls, saving ~3kb. Nuxt configures this automatically; standalone Vue apps must opt in. source -
Pass
{ mode: 'server' }touseHead()for static SEO metadata (Open Graph images, robots, schema.org) that doesn't need client-side reactivity — this strips the tags from the client bundle entirely. Similarly use{ mode: 'client' }for analytics scripts to keep them out of SSR output. Caveat:titleTemplatemust be included in both environments to avoid title flashing. source -
Use
tagPosition: 'bodyClose'for non-critical scripts (analytics, chat widgets) instead ofhead— this prevents render-blocking and improves page load performance. UsetagPriority: 'critical' | 'high' | 'low'aliases rather than raw numbers to preserve Capo.js-derived ordering weights that Unhead applies automatically. source -
Use
textContentinstead ofinnerHTMLfor inline scripts and styles —textContentescapes HTML characters, preventing injection. Only useinnerHTMLwhen HTML entities are required, and sanitize the content yourself (e.g. with DOMPurify). For user-generated inline content, preferuseHeadSafe()which restricts scripts totype="application/json"only. source -
Register
TemplateParamsPluginand define globaltemplateParams(e.g.siteName,separator) once in your head instance setup rather than repeating them per page. These params work across all head tags — includingog:titleandmetadescriptions — not justtitleTemplate. Set%separatorto a smart separator like·or—; it auto-removes when adjacent to empty content. source -
Use
InferSeoMetaPluginto automatically deriveog:titleandog:descriptionfrom existingtitleanddescriptiontags, eliminating manual duplication. ConfigureogTitlewith a transform function to strip the site name suffix from Open Graph titles (e.g. removing"| My Site"thattitleTemplateappends). source