Skip to content

Latest commit

 

History

History
403 lines (280 loc) · 8.8 KB

migration.md

File metadata and controls

403 lines (280 loc) · 8.8 KB
title description navigation
Migrate to Unhead v2
Learn about how to migrate to Unhead v2 from v1
title
Upgrade Guide

Introduction

The goal 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:

::code-group

-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

::tip Nuxt should not be effected by this change. ::

⚠️ Breaking Changes:

  • createServerHead(){lang="ts"} and createHead(){lang="ts"} exports from unhead 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.

::TabComparison

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()

Client bundle:

-import { createHead } from '@unhead/vue'
+import { createHead } from '@unhead/vue/client'
import { createApp } from 'vue'

const app = createApp()
const head = createHead()
app.use(head)

Server bundle:

-import { createServerHead } from '@unhead/vue'
+import { createHead } from '@unhead/vue/server'
import { createApp } from 'vue'

const app = createApp()

-const head = createServerHead()
+const head = createHead()

app.use(head)

::

Removed Implicit Context

🚦 Impact Level: Critical

::tip Nuxt should not be effected by this change. ::

⚠️ Breaking Changes:

  • getActiveHead(){lang="ts"}, activeHead{lang="ts"} exports are removed
  • Vue Only: setHeadInjectionHandler(){lang="ts"} is removed
  • Vue Only: Error may be thrown when using useHead(){lang="ts"} after async operations

The implicit context implementation kept a global instance of Unhead available so that you could use the useHead(){lang="ts"} 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.

::note Passing the instance is only relevant if you're importing from unhead. In JavaScript frameworks we tie the context to the framework itself so you don't need to worry about this. ::

::code-group

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.

::code-group

<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(){lang="ts"} about context, check the new documentation:

Removed vmid, hid, children, body

🚦 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.

::Caution When migrating your code ensure that you're not dynamically setting 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]
})

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]
})

Updated useScript(){lang="ts"}

🚦 Impact Level: High

⚠️ Breaking Changes:

  • useScript(){lang="ts"} composable is now exported from @unhead/scripts
  • Script instance is no longer augmented as a proxy and promise
  • script.proxy{lang="ts"} is rewritten for simpler, more stable behavior
  • stub(){lang="ts"} and runtime hook script:instance-fn are removed

You will need to update any useScript(){lang="ts"} calls to be from @unhead/scripts.

::TabComparison

-import { useScript } from 'unhead'
+import { useScript } from '@unhead/scripts'
-import { useScript } from '@unhead/vue'
+import { useScript } from '@unhead/scripts/vue'

::

Legacy Subpath Export

For the underlying logic changes, you can opt-in the previous behavior by importing from the legacy subpath export.

import { useScript } from '@unhead/scripts/legacy'

// same behavior as v1
useScript()
import { useScript } from '@unhead/scripts/vue-legacy'

// same behavior as v1
useScript()

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: :UBadge{color="success" variant="subtle" size="sm" label="Low"}

An optional Capo.js plugin was added to Unhead, in v2 we make this the default sorting behavior.

::warning As all head tags may be re-ordered this will break any snapshot tests that you have in place and in some rare cases may lead to performance regressions. ::

You can opt-out of Capo.js sorting by providing the option.

createHead({
  disableCapoSorting: true,
})