Migrate to Unhead v2
Introduction
While Unhead has always been framework agnostic, the majority of adoption was by the Vue ecosystem.
With the release of Unhead v2, we now have first-class support for other frameworks. However, this guide will focus on the changes that affect Vue and TypeScript users.
The high-level of Unhead v2 was to remove deprecations and remove the implicit context implementation.
Legacy Support
Unhead v2 is mostly fully backwards compatible with Unhead v1.
While not recommended, if upgrading is not possible for you, you can change your imports to the following:
-import { createServerHead, useHead } from 'unhead'
+import { createServerHead, useHead } from 'unhead/legacy'
-import { createServerHead, useHead } from '@unhead/vue'
+import { createServerHead, useHead } from '@unhead/vue/legacy'
This will be removed in a future minor version, so you should lock your dependencies to the version that works for you.
Client / Server Subpath Exports
๐ฆ Impact Level: Critical
โ ๏ธ Breaking Changes:
createServerHead()
andcreateHead()
exports fromunhead
are removed
The path where you import createHead
from has been updated to be a subpath export.
Please follow the updated installation instructions or simply update the import to use the subpath.
Client bundle:
-import { createServerHead } from 'unhead'
+import { createHead } from 'unhead/client'
// avoids bundling server plugins
createHead()
Server bundle:
-import { createServerHead } from 'unhead'
+import { createHead } from 'unhead/server'
// avoids bundling server plugins
-createServerHead()
+createHead()
Removed Implicit Context
๐ฆ Impact Level: Critical
โ ๏ธ Breaking Changes:
getActiveHead()
,activeHead
exports are removed- Vue Only:
setHeadInjectionHandler()
is removed - Vue Only: Error may be thrown when using
useHead()
after async operations
The implicit context implementation kept a global instance of Unhead available so that you could use the useHead()
composables
anywhere in your application.
useHead({
title: 'This just worked!'
})
While safe client side, this was a leaky abstraction server side and led to memory leaks in some cases.
In v2, the core composables no longer have access to the Unhead instance. Instead, you must pass the Unhead instance to the composables.
unhead
. In JavaScript frameworks we tie the context to the framework itself so you
don't need to worry about this.import { useHead } from 'unhead'
// example of getting the instance
const unheadInstance = useMyApp().unhead
useHead(unheadInstance, {
title: 'Looks good'
})
import { useHead } from 'unhead'
useHead({
title: 'Just worked! But with SSR issues'
})
For frameworks users, you may run into issues with the context being lost.
<script setup lang="ts">
// In Vue this happens in lifecycle hooks where we have async operations.
onMounted(async () => {
await fetchSomeData()
useHead({
title: 'This will not work'
})
})
</script>
If you're getting errors on your useHead()
about context, check the new documentation:
vmid
, hid
, children
, body
Removed
๐ฆ Impact Level: High
For legacy support with Vue Meta we allowed end users to provide deprecated properties: vmid
, hid
, children
and body
.
You must either update these properties to the appropriate replacement, remove them, or you can use the DeprecationsPlugin
.
Meta tags with vmid
, hid
These are already deduped magically so you can safely remove them there.
useHead({
meta: [{
name: 'description',
- vmid: 'description'
- hid: 'description'
}]
})
Other Tags with vmid
, hid
Use key
if you need the deduplication feature. This is useful for tags that may change from server to client
rendering.
useHead({
script: [{
- vmid: 'my-key'
- hid: 'my-key'
+ key: 'my-key',
}]
})
Using children
The children
key is a direct replacement of innerHTML
which you should use instead.
innerHTML
as this can lead to XSS vulnerabilities.useHead({
script: [
{
- children: '..'
+ innerHTML: '..'
}
]
})
Using body
The body
key should be updated to use the Tag Position feature.
useHead({
script: [
{
- body: true
+ tagPosition: 'bodyClose'
}
]
})
Use Deprecations Plugin
import { createHead } from 'unhead'
import { DeprecationsPlugin } from 'unhead/optionalPlugins'
const unhead = createHead({
plugins: [DeprecationsPlugin]
})
Opt-in Template Params & Tag Alias Sorting
๐ฆ Impact Level: High
To reduce the bundle size and improve performance, we've moved the template params and tag alias sorting to optional plugins.
If you'd like to continue using these, please opt-in to the plugins.
import { AliasSortingPlugin, TemplateParamsPlugin } from 'unhead/plugins'
createHead({
plugins: [TemplateParamsPlugin, AliasSortingPlugin]
})
Vue 2 Support
๐ฆ Impact Level: Critical
Unhead v2 no longer supports Vue v2. If you're using Vue v2, you will need to lock your dependencies to the latest v1 version of Unhead.
Promise Input Support
๐ฆ Impact Level: Medium
If you have promises as input they will no longer be resolved, either await the promise before passing it along or register the optional promises plugin.
Option 1: Await Promise
useHead({
link: [
{
- href: import('~/assets/MyFont.css?url'),
+ href: await import('~/assets/MyFont.css?url'),
rel: 'stylesheet',
type: 'text/css'
}
]
})
Option 2: Promise Plugin
import { PromisePlugin } from 'unhead/optionalPlugins'
const unhead = createHead({
plugins: [PromisePlugin]
})
useScript()
Updated
๐ฆ Impact Level: High
โ ๏ธ Breaking Changes:
- Script instance is no longer augmented as a proxy and promise
script.proxy
is rewritten for simpler, more stable behaviorstub()
and runtime hookscript:instance-fn
are removed
Replacing promise usage
If you're using the script as a promise you should instead opt to use the onLoaded()
functions.
const script = useScript()
-script.then(() => console.log('loaded')
+script.onLoaded(() => console.log('loaded'))
Replacing proxy usage
If you're accessing the underlying API directly from the script instance, you will now need to only access it from the .proxy
.
const script = useScript('..', {
use() { return { foo: [] } }
})
-script.foo.push('bar')
+script.proxy.foo.push('bar')
Replacing stub()
If you were using stub for anything you should replace this with either custom use()
behavior.
const script = useScript('...', {
- stub() { return { foo: import.meta.server ? [] : undefined } }
})
+script.proxy = {} // your own implementation
Tag Sorting Updated
๐ฆ Impact Level: Low
An optional Capo.js plugin was added to Unhead, in v2 we make this the default sorting behavior.
You can opt-out of Capo.js sorting by providing the option.
createHead({
disableCapoSorting: true,
})
Default SSR Tags
๐ฆ Impact Level: Low
When SSR Unhead will now insert important default tags for you:
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<html lang="en">
If you were previously relying on these being left empty, you may need to either disable them by using disableDefaultTags
or insert tags
to override them.
import { createHead } from '@unhead/vue/server'
// disable when creating the head instance
createHead({
disableDefaults: true,
})
import { useHead } from 'unhead'
// override the defaults
useHead({
htmlAttrs: {
lang: 'fr'
}
})
CJS Exports Removed
๐ฆ Impact Level: Low
CommonJS exports have been removed in favor of ESM only.
-const { createHead } = require('unhead/client')
+import { createHead } from 'unhead/client'
@unhead/schema
Deprecated
๐ฆ Impact Level: Low
The @unhead/schema
package is now deprecated and will be removed in a future version. You should instead import
the schema from unhead
or @unhead/vue
.
-import { HeadTag } from '@unhead/schema'
+import { HeadTag } from 'unhead/types'