Migrate to v3
- Automated Migration Checks
- @unhead/addons โ @unhead/bundler
- Framework Vite Plugins: Named Unhead Export
- Legacy Property Names
- Schema.org Plugin
- Server Composables Removed
- Core API Changes
- Vue Legacy Exports
- Server Utilities
- Hooks
- Type Changes
- Strict Type Narrowing for Link, Script, and Meta
- Other API Changes
- Quick Reference: Import Changes
Unhead v3 changes several core defaults and removes v2 compatibility behavior. This guide covers the breaking changes and the deprecated compatibility APIs that remain temporarily available.
Automated Migration Checks
Add ValidatePlugin during the upgrade to report v2 patterns:
import { ValidatePlugin } from 'unhead/plugins'
const head = createHead({
plugins: [
ValidatePlugin() // Detects deprecated props, missing plugins, and more
]
})
The plugin will warn you about:
- Missing
TemplateParamsPlugin: template params like%siteNameare now opt-in and will appear literally without the plugin - Missing
AliasSortingPlugin:before:/after:tag priorities are now opt-in and will be silently ignored without the plugin - Deprecated property names:
children,hid,vmid,body: trueare no longer auto-converted - Removed
modeoption:{ mode: 'server' }onhead.push()is silently ignored
All rules use ESLint-style config and can be individually disabled:
ValidatePlugin({
rules: {
'missing-template-params-plugin': 'off',
}
})
The unified Vite plugin injects ValidatePlugin in development unless validate is disabled.
Remove ValidatePlugin once your migration is complete, or keep it for ongoing validation.
@unhead/addons โ @unhead/bundler
๐ฆ Impact Level: High (only if you import build plugins manually)
The @unhead/addons package has been renamed to @unhead/bundler. The old package still works as a deprecation shim that re-exports from @unhead/bundler, but logs a runtime warning.
- pnpm add -D @unhead/addons
+ pnpm add -D @unhead/bundler
The default export has also been replaced with a named Unhead export:
- import unhead from '@unhead/addons/vite'
+ import { Unhead } from '@unhead/bundler/vite'
export default defineConfig({
- plugins: [unhead()],
+ plugins: [Unhead()],
})
@unhead/bundler and wires up framework-specific runtime plugins:import { Unhead } from '@unhead/vue/vite'
// or @unhead/react/vite, @unhead/svelte/vite, @unhead/solid-js/vite
webpack consumers should use the framework bundler entry:
- import unhead from '@unhead/addons/webpack'
+ import { Unhead } from '@unhead/vue/bundler'
- plugins: [unhead()]
+ plugins: Unhead().webpack()
The minify backend subpaths have moved too:
- import { createJSMinifier } from '@unhead/addons/minify/rolldown'
- import { createCSSMinifier } from '@unhead/addons/minify/lightningcss'
+ import { createJSMinifier } from '@unhead/bundler/minify/rolldown'
+ import { createCSSMinifier } from '@unhead/bundler/minify/lightningcss'
See the Build Plugins overview for the new options table.
Framework Vite Plugins: Named Unhead Export
๐ฆ Impact Level: High
Every framework Vite plugin now exports a named Unhead symbol instead of a default export. Update your vite.config.ts:
// Vue
- import unhead from '@unhead/vue/vite'
+ import { Unhead } from '@unhead/vue/vite'
// React
- import unhead from '@unhead/react/vite'
+ import { Unhead } from '@unhead/react/vite'
// Svelte
- import unhead from '@unhead/svelte/vite'
+ import { Unhead } from '@unhead/svelte/vite'
// Solid
- import unhead from '@unhead/solid-js/vite'
+ import { Unhead } from '@unhead/solid-js/vite'
export default defineConfig({
- plugins: [unhead()],
+ plugins: [Unhead()],
})
The plugin call remains the same after the import change. Nuxt users do not configure this plugin directly.
Legacy Property Names
๐ฆ Impact Level: High
The current createHead() entry points omit DeprecationsPlugin. Rename these properties before upgrading; the deprecated plugin is still available for staged migrations.
children โ innerHTML
useHead({
script: [{
- children: 'console.log("hello")',
+ innerHTML: 'console.log("hello")',
}]
})
hid / vmid โ key
useHead({
meta: [{
- hid: 'description',
+ key: 'description',
name: 'description',
content: 'My description'
}]
})
useHead({
meta: [{
- vmid: 'og:title',
+ key: 'og:title',
property: 'og:title',
content: 'My Title'
}]
})
body: true โ tagPosition: 'bodyClose'
useHead({
script: [{
src: '/script.js',
- body: true,
+ tagPosition: 'bodyClose',
}]
})
Quick Reference
| Old Property | New Property |
|---|---|
children | innerHTML |
hid | key |
vmid | key |
body: true | tagPosition: 'bodyClose' |
Schema.org Plugin
๐ฆ Impact Level: High
The PluginSchemaOrg and SchemaOrgUnheadPlugin exports have been removed. Use UnheadSchemaOrg instead.
- import { PluginSchemaOrg } from '@unhead/schema-org'
+ import { UnheadSchemaOrg } from '@unhead/schema-org'
const head = createHead({
plugins: [
- PluginSchemaOrg()
+ UnheadSchemaOrg()
]
})
For Vue users:
- import { PluginSchemaOrg } from '@unhead/schema-org/vue'
+ import { UnheadSchemaOrg } from '@unhead/schema-org/vue'
Schema.org Config Options
The following config options have been removed:
| Removed Option | Replacement |
|---|---|
canonicalHost | host |
canonicalUrl | path + host |
position | tagPosition |
defaultLanguage | inLanguage |
defaultCurrency | currency |
UnheadSchemaOrg({
- canonicalHost: 'https://example.com',
- canonicalUrl: 'https://example.com/page',
+ host: 'https://example.com',
+ path: '/page',
})
Server Composables Removed
๐ฆ Impact Level: Medium-High
The useServerHead, useServerHeadSafe, and useServerSeoMeta composables have been removed. Use the standard composables instead.
- import { useServerHead, useServerSeoMeta } from 'unhead'
+ import { useHead, useSeoMeta } from 'unhead'
- useServerHead({ title: 'My Page' })
+ useHead({ title: 'My Page' })
- useServerSeoMeta({ description: 'My description' })
+ useSeoMeta({ description: 'My description' })
If you need server-only head management, use conditional logic:
if (import.meta.server) {
useHead({ title: 'Server Only' })
}
Core API Changes
๐ฆ Impact Level: Medium
createHeadCore โ Platform createHead
- import { createHeadCore } from 'unhead'
+ import { createHead } from 'unhead/client'
- const head = createHeadCore()
+ const head = createHead()
For SSR, import createHead from unhead/server. The low-level createUnhead() API now requires a renderer and is intended for adapter authors.
headEntries() โ entries Map
- const entries = head.headEntries()
+ const entries = [...head.entries.values()]
mode Option Removed
The mode option on head entries has been removed. Runtime mode detection is no longer supported.
head.push({
title: 'My Page',
- }, { mode: 'server' })
+ })
Use the appropriate createHead function instead:
// Client-side
import { createHead } from 'unhead/client'
// Server-side
import { createHead } from 'unhead/server'
Vue Legacy Exports
๐ฆ Impact Level: Medium
/legacy Export Path Deprecated
The @unhead/vue/legacy import still works but is deprecated and scheduled for removal in v4. Update to the explicit client or server import:
- import { createHead } from '@unhead/vue/legacy'
+ import { createHead } from '@unhead/vue/client'
// or for SSR
+ import { createHead } from '@unhead/vue/server'
createHeadCore Removed
- import { createHeadCore } from '@unhead/vue'
+ import { createHead } from '@unhead/vue/server'
// or for client
+ import { createHead } from '@unhead/vue/client'
Server Utilities
๐ฆ Impact Level: Low
extractUnheadInputFromHtml โ parseHtmlForUnheadExtraction
The function has been moved from unhead/server to unhead/parser.
- import { extractUnheadInputFromHtml } from 'unhead/server'
+ import { parseHtmlForUnheadExtraction } from 'unhead/parser'
- const { input } = extractUnheadInputFromHtml(html)
+ const { input } = parseHtmlForUnheadExtraction(html)
Hooks
๐ฆ Impact Level: Low
The init hook was removed. dom:renderTag remains in the type definitions for compatibility but is deprecated and no longer called internally. dom:rendered is also deprecated but is still emitted; prefer the onRendered entry option for entry-specific work.
The dom:beforeRender hook is now synchronous and renderDOMHead no longer returns a Promise:
- await renderDOMHead(head, { document })
+ renderDOMHead(head, { document })
The SSR hooks (ssr:beforeRender, ssr:render, ssr:rendered) are now synchronous and renderSSRHead no longer returns a Promise:
- const head = await renderSSRHead(head)
+ const head = renderSSRHead(head)
Type Changes
๐ฆ Impact Level: Low
| Removed Type | Replacement |
|---|---|
Head | SerializableHead |
ResolvedHead | SerializableHead |
MergeHead | Use generics directly |
MetaFlatInput | MetaFlat |
ResolvedMetaFlat | MetaFlat |
RuntimeMode | Removed (no replacement needed) |
- import type { Head, MetaFlatInput, RuntimeMode } from 'unhead/types'
+ import type { MetaFlat, SerializableHead } from 'unhead/types'
Strict Type Narrowing for Link, Script, and Meta
๐ฆ Impact Level: Medium
The Link and Script types are now strict discriminated unions. Known rel and type values enforce per-tag required properties at the type level. Use the new defineLink and defineScript helpers to declare custom values without losing strictness on known ones.
Link Tags
Known rel values now enforce their required properties. For example, preloading a font requires crossorigin:
useHead({
link: [{
rel: 'preload',
as: 'font',
href: '/font.woff2',
+ crossorigin: 'anonymous', // now required for font preloads
}]
})
For non-standard rel values not covered by KnownLinkRel (e.g., OpenID endpoints, RSD links), use defineLink:
import { defineLink, useHead } from 'unhead'
useHead({
link: [
defineLink({ rel: 'openid2.provider', href: 'https://example.com/openid' }),
]
})
Script Tags
Inline scripts must have textContent or innerHTML and cannot include src, async, or defer. For custom type values, use defineScript:
import { defineScript, useHead } from 'unhead'
useHead({
script: [
defineScript({ type: 'text/plain', textContent: '...' }),
]
})
Meta Content Required
Meta content is now required on name, property, and http-equiv meta tags. Use null explicitly to remove a meta tag:
- useHead({ meta: [{ name: 'description' }] }) // no longer valid
+ useHead({ meta: [{ name: 'description', content: null }] }) // removes the tag
String Variables
When rel or type comes from a variable typed as string, TypeScript cannot narrow the union. Wrap it with defineLink / defineScript or use as const for literals:
import { defineLink, useHead } from 'unhead'
const rel = getRelFromConfig() // string, not a literal
useHead({
link: [defineLink({ rel, href: '/path' })]
})
// or use as const for literals
const link = { rel: 'canonical' as const, href: '/path' }
useHead({ link: [link] })
Other API Changes
resolveScriptKey: Internal utility, no longer exportedsetHeadInjectionHandler(Vue) : Head injection is handled automaticallyDeprecationsPluginand Vue'sresolveUnrefHeadInputremain available as compatibility helpers. Avoid them in new code.
Quick Reference: Import Changes
// Build plugins
- import unhead from '@unhead/addons/vite'
+ import { Unhead } from '@unhead/bundler/vite'
// or, recommended, from your framework subpath:
+ import { Unhead } from '@unhead/vue/vite'
// Legacy properties - update property names directly; do not depend on the compatibility plugin
// Schema.org
- import { PluginSchemaOrg, SchemaOrgUnheadPlugin } from '@unhead/schema-org'
+ import { UnheadSchemaOrg } from '@unhead/schema-org'
// Server composables
- import { useServerHead, useServerHeadSafe, useServerSeoMeta } from 'unhead'
+ import { useHead, useHeadSafe, useSeoMeta } from 'unhead'
// Core
- import { createHeadCore } from 'unhead'
+ import { createHead } from 'unhead/client'
+ import { createHead } from 'unhead/server'
// Server utilities
- import { extractUnheadInputFromHtml } from 'unhead/server'
+ import { parseHtmlForUnheadExtraction } from 'unhead/parser'
// Vue
- import { createHeadCore, setHeadInjectionHandler } from '@unhead/vue'
- import { ... } from '@unhead/vue/legacy'
+ import { createHead } from '@unhead/vue/client'
+ import { createHead } from '@unhead/vue/server'
- Automated Migration Checks
- @unhead/addons โ @unhead/bundler
- Framework Vite Plugins: Named Unhead Export
- Legacy Property Names
- Schema.org Plugin
- Server Composables Removed
- Core API Changes
- Vue Legacy Exports
- Server Utilities
- Hooks
- Type Changes
- Strict Type Narrowing for Link, Script, and Meta
- Other API Changes
- Quick Reference: Import Changes