diff --git a/docs/0.typescript/head/guides/0.get-started/1.migration.md b/docs/0.typescript/head/guides/0.get-started/1.migration.md deleted file mode 100644 index bf2b45a96..000000000 --- a/docs/0.typescript/head/guides/0.get-started/1.migration.md +++ /dev/null @@ -1,355 +0,0 @@ ---- -title: Upgrade Guide -description: Learn how to migrate between Unhead versions for TypeScript users. -navigation: - title: Upgrade Guide ---- - -## Migrate to v3 (from v2) - -Unhead v3 removes all deprecated APIs and focuses on performance improvements. This guide covers the breaking changes. - -### Legacy Property Names - -🚦 Impact Level: High - -The `DeprecationsPlugin` that automatically converted legacy property names has been removed. You must update your head entries to use the current property names. - -**`children` → `innerHTML`** - -```diff -useHead({ - script: [{ -- children: 'console.log("hello")', -+ innerHTML: 'console.log("hello")', - }] -}) -``` - -**`hid` / `vmid` → `key`** - -```diff -useHead({ - meta: [{ -- hid: 'description', -+ key: 'description', - name: 'description', - content: 'My description' - }] -}) -``` - -**`body: true` → `tagPosition: 'bodyClose'`** - -```diff -useHead({ - script: [{ - src: '/script.js', -- body: true, -+ tagPosition: 'bodyClose', - }] -}) -``` - -### Schema.org Plugin - -🚦 Impact Level: High - -The `PluginSchemaOrg` and `SchemaOrgUnheadPlugin` exports have been removed. Use `UnheadSchemaOrg` instead. - -```diff -- import { PluginSchemaOrg } from '@unhead/schema-org' -+ import { UnheadSchemaOrg } from '@unhead/schema-org' - -const head = createHead({ - plugins: [ -- PluginSchemaOrg() -+ UnheadSchemaOrg() - ] -}) -``` - -### Server Composables Removed - -🚦 Impact Level: Medium-High - -The `useServerHead`, `useServerHeadSafe`, and `useServerSeoMeta` composables have been removed. Use the standard composables instead. - -```diff -- 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: - -```ts -if (import.meta.server) { - useHead({ title: 'Server Only' }) -} -``` - -### Core API Changes - -🚦 Impact Level: Medium - -**`createHeadCore` → `createUnhead`** - -```diff -- import { createHeadCore } from 'unhead' -+ import { createUnhead } from 'unhead' - -- const head = createHeadCore() -+ const head = createUnhead() -``` - -**`headEntries()` → `entries` Map** - -```diff -- const entries = head.headEntries() -+ const entries = [...head.entries.values()] -``` - -**`mode` Option Removed** - -The `mode` option on head entries has been removed. - -```diff -head.push({ - title: 'My Page', -- }, { mode: 'server' }) -+ }) -``` - -### Hooks - -🚦 Impact Level: Low - -The following hooks have been removed: - -- `init` - No longer needed -- `dom:renderTag` - DOM rendering is now synchronous -- `dom:rendered` - Use the `onRendered` option on `useHead()` instead - -The `dom:beforeRender` hook is now synchronous and `renderDOMHead` no longer returns a Promise: - -```diff -- 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: - -```diff -- const head = await renderSSRHead(head) -+ const head = renderSSRHead(head) -``` - -### Type Changes - -🚦 Impact Level: Low - -| Removed Type | Replacement | -|-------------|-------------| -| `Head` | `HeadTag` or specific tag types | -| `ResolvedHead` | `ResolvedHeadTag` | -| `MetaFlatInput` | `MetaFlat` | -| `RuntimeMode` | Removed (no replacement needed) | - -```diff -- import type { Head, MetaFlatInput, RuntimeMode } from 'unhead' -+ import type { HeadTag, MetaFlat } from 'unhead' -``` - -### Other Removed APIs - -- `resolveScriptKey` - Internal utility, no longer exported -- `DeprecationsPlugin` - Update property names directly instead -- `extractUnheadInputFromHtml` - Use `parseHtmlForUnheadExtraction` from `unhead/parser` - ---- - -## Migrate to v2 (from v1) - -With the release of Unhead v2, we now have first-class support for other frameworks. This section covers the v1 to v2 migration. - -### Client / Server Subpath Exports - -🚦 Impact Level: Critical - -**⚠️ 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. - -**Client bundle:** - -```diff --import { createServerHead } from 'unhead' -+import { createHead } from 'unhead/client' - -// avoids bundling server plugins -createHead() -``` - -**Server bundle:** - -```diff --import { createServerHead } from 'unhead' -+import { createHead } from 'unhead/server' - -// avoids bundling server plugins --createServerHead() -+createHead() -``` - -### Removed Implicit Context - -🚦 Impact Level: Critical - -**⚠️ Breaking Changes:** - -- `getActiveHead()`{lang="ts"}, `activeHead`{lang="ts"} exports are removed - -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. - -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. -:: - -```ts [TypeScript v2] -import { useHead } from 'unhead' - -// example of getting the instance -const unheadInstance = useMyApp().unhead -useHead(unheadInstance, { - title: 'Looks good' -}) -``` - -### 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 update these properties to the appropriate replacement or remove them. See the [v3 migration section](#legacy-property-names) for the replacements. - -### 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. - -```ts -import { AliasSortingPlugin, TemplateParamsPlugin } from 'unhead/plugins' - -createHead({ - plugins: [TemplateParamsPlugin, AliasSortingPlugin] -}) -``` - -### 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. - -```ts -import { PromisePlugin } from 'unhead/plugins' - -const unhead = createHead({ - plugins: [PromisePlugin] -}) -``` - -### Updated `useScript()`{lang="ts"} - -🚦 Impact Level: High - -**⚠️ Breaking Changes:** - -- 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 - -**Replacing promise usage** - -```diff -const script = useScript() - --script.then(() => console.log('loaded') -+script.onLoaded(() => console.log('loaded')) -``` - -**Replacing proxy usage** - -```diff -const script = useScript('..', { - use() { return { foo: [] } } -}) - --script.foo.push('bar') -+script.proxy.foo.push('bar') -``` - -### Tag Sorting Updated - -🚦 Impact Level: Low - -[Capo.js](https://rviscomi.github.io/capo.js/) sorting is now the default. You can opt-out: - -```ts -createHead({ - disableCapoSorting: true, -}) -``` - -### Default SSR Tags - -🚦 Impact Level: Low - -When SSR Unhead will now insert important default tags for you: - -- `` -- `` -- `` - -```ts -import { createHead } from 'unhead/server' - -// disable when creating the head instance -const head = createHead({ - disableDefaults: true, -}) -``` - -### CJS Exports Removed - -🚦 Impact Level: Low - -CommonJS exports have been removed in favor of ESM only. - -```diff --const { createHead } = require('unhead/client') -+import { createHead } from 'unhead/client' -``` - -### Deprecated `@unhead/schema` - -🚦 Impact Level: Low - -The `@unhead/schema` package is deprecated. Import from `unhead/types` or `unhead` instead. - -```diff --import { HeadTag } from '@unhead/schema' -+import { HeadTag } from 'unhead/types' -``` diff --git a/docs/content/6.migration-guide/1.v3.md b/docs/content/6.migration-guide/1.v3.md new file mode 100644 index 000000000..0195366cc --- /dev/null +++ b/docs/content/6.migration-guide/1.v3.md @@ -0,0 +1,353 @@ +--- +title: "Migrate to v3" +description: "Migrate from Unhead v2 to v3. Covers all breaking changes, removed APIs, and their replacements." +navigation: + title: "v3" +--- + +Unhead v3 removes all deprecated APIs and focuses on performance improvements. This guide covers the breaking changes. + +## Automated Migration Checks + +Add `ValidatePlugin` during your upgrade to automatically detect v2 patterns and get actionable warnings: + +```ts +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 `%siteName` are 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: true` are no longer auto-converted +- **Removed `mode` option** : `{ mode: 'server' }` on `head.push()` is silently ignored + +All rules use ESLint-style config and can be individually disabled: + +```ts +ValidatePlugin({ + rules: { + 'missing-template-params-plugin': 'off', + } +}) +``` + +Remove `ValidatePlugin` once your migration is complete, or keep it for ongoing validation. + +--- + +## Legacy Property Names + +🚦 Impact Level: **High** + +The `DeprecationsPlugin` that automatically converted legacy property names has been removed. You must update your head entries to use the current property names. + +### `children` → `innerHTML` + +```diff +useHead({ + script: [{ +- children: 'console.log("hello")', ++ innerHTML: 'console.log("hello")', + }] +}) +``` + +### `hid` / `vmid` → `key` + +```diff +useHead({ + meta: [{ +- hid: 'description', ++ key: 'description', + name: 'description', + content: 'My description' + }] +}) +``` + +```diff +useHead({ + meta: [{ +- vmid: 'og:title', ++ key: 'og:title', + property: 'og:title', + content: 'My Title' + }] +}) +``` + +### `body: true` → `tagPosition: 'bodyClose'` + +```diff +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. + +```diff +- import { PluginSchemaOrg } from '@unhead/schema-org' ++ import { UnheadSchemaOrg } from '@unhead/schema-org' + +const head = createHead({ + plugins: [ +- PluginSchemaOrg() ++ UnheadSchemaOrg() + ] +}) +``` + +For Vue users: + +```diff +- 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` | Use `tagPosition` on individual schema entries | +| `defaultLanguage` | Use `inLanguage` on schema nodes | +| `defaultCurrency` | Use `priceCurrency` on schema nodes | + +```diff +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. + +```diff +- 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: + +```ts +if (import.meta.server) { + useHead({ title: 'Server Only' }) +} +``` + +--- + +## Core API Changes + +🚦 Impact Level: **Medium** + +### `createHeadCore` → `createUnhead` + +```diff +- import { createHeadCore } from 'unhead' ++ import { createUnhead } from 'unhead' + +- const head = createHeadCore() ++ const head = createUnhead() +``` + +### `headEntries()` → `entries` Map + +```diff +- 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. + +```diff +head.push({ + title: 'My Page', +- }, { mode: 'server' }) ++ }) +``` + +Use the appropriate `createHead` function instead: + +```ts +// 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 emits a runtime deprecation warning. Update to the explicit client or server import: + +```diff +- import { createHead } from '@unhead/vue/legacy' ++ import { createHead } from '@unhead/vue/client' +// or for SSR ++ import { createHead } from '@unhead/vue/server' +``` + +### `createHeadCore` Removed + +```diff +- 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`. + +```diff +- import { extractUnheadInputFromHtml } from 'unhead/server' ++ import { parseHtmlForUnheadExtraction } from 'unhead/parser' + +- const { input } = extractUnheadInputFromHtml(html) ++ const { input } = parseHtmlForUnheadExtraction(html) +``` + +--- + +## Hooks + +🚦 Impact Level: **Low** + +The following hooks have been removed: + +- `init` : No longer needed +- `dom:renderTag` : DOM rendering is now synchronous +- `dom:rendered` : Use the `onRendered` option on `useHead()` instead + +The `dom:beforeRender` hook is now synchronous and `renderDOMHead` no longer returns a Promise: + +```diff +- 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: + +```diff +- const head = await renderSSRHead(head) ++ const head = renderSSRHead(head) +``` + +--- + +## Type Changes + +🚦 Impact Level: **Low** + +| Removed Type | Replacement | +|-------------|-------------| +| `Head` | `HeadTag` or specific tag types | +| `ResolvedHead` | `ResolvedHeadTag` | +| `MergeHead` | Use generics directly | +| `MetaFlatInput` | `MetaFlat` | +| `ResolvedMetaFlat` | `MetaFlat` | +| `RuntimeMode` | Removed (no replacement needed) | + +```diff +- import type { Head, MetaFlatInput, RuntimeMode } from 'unhead' ++ import type { HeadTag, MetaFlat } from 'unhead' +``` + +--- + +## Other Removed APIs + +- `resolveScriptKey` : Internal utility, no longer exported +- `DeprecationsPlugin` : Update property names directly instead +- `resolveUnrefHeadInput` (Vue) : Reactive resolution now happens automatically +- `setHeadInjectionHandler` (Vue) : Head injection is handled automatically + +--- + +## Quick Reference: Import Changes + +```diff +// Legacy properties - update property names directly, no plugin needed +- import { DeprecationsPlugin } from 'unhead/plugins' + +// 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 { createUnhead } from 'unhead' + +// Server utilities +- import { extractUnheadInputFromHtml } from 'unhead/server' ++ import { parseHtmlForUnheadExtraction } from 'unhead/parser' + +// Vue +- import { createHeadCore, resolveUnrefHeadInput, setHeadInjectionHandler } from '@unhead/vue' +- import { ... } from '@unhead/vue/legacy' ++ import { createHead } from '@unhead/vue/client' ++ import { createHead } from '@unhead/vue/server' +``` diff --git a/docs/content/6.migration-guide/2.v2.md b/docs/content/6.migration-guide/2.v2.md new file mode 100644 index 000000000..07d5eb3ad --- /dev/null +++ b/docs/content/6.migration-guide/2.v2.md @@ -0,0 +1,202 @@ +--- +title: "Migrate to v2" +description: "Migrate from Unhead v1 to v2. Covers subpath exports, removed implicit context, opt-in plugins, and more." +navigation: + title: "v2" +--- + +With the release of Unhead v2, we now have first-class support for other frameworks. This guide covers the v1 to v2 migration. + +## Client / Server Subpath Exports + +🚦 Impact Level: **Critical** + +**⚠️ 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. + +**Client bundle:** + +```diff +-import { createServerHead } from 'unhead' ++import { createHead } from 'unhead/client' + +// avoids bundling server plugins +createHead() +``` + +**Server bundle:** + +```diff +-import { createServerHead } from 'unhead' ++import { createHead } from 'unhead/server' + +// avoids bundling server plugins +-createServerHead() ++createHead() +``` + +--- + +## Removed Implicit Context + +🚦 Impact Level: **Critical** + +**⚠️ Breaking Changes:** + +- `getActiveHead()`{lang="ts"}, `activeHead`{lang="ts"} exports are removed + +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. + +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. +:: + +```ts [TypeScript v2] +import { useHead } from 'unhead' + +// example of getting the instance +const unheadInstance = useMyApp().unhead +useHead(unheadInstance, { + title: 'Looks good' +}) +``` + +--- + +## 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 update these properties to the appropriate replacement or remove them. See the [v3 migration guide](/docs/content/migration-guide/v3#legacy-property-names) for the replacements. + +--- + +## 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. + +```ts +import { AliasSortingPlugin, TemplateParamsPlugin } from 'unhead/plugins' + +createHead({ + plugins: [TemplateParamsPlugin, AliasSortingPlugin] +}) +``` + +--- + +## 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. + +```ts +import { PromisePlugin } from 'unhead/plugins' + +const unhead = createHead({ + plugins: [PromisePlugin] +}) +``` + +--- + +## Updated `useScript()`{lang="ts"} + +🚦 Impact Level: **High** + +**⚠️ Breaking Changes:** + +- 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 + +**Replacing promise usage** + +```diff +const script = useScript() + +-script.then(() => console.log('loaded') ++script.onLoaded(() => console.log('loaded')) +``` + +**Replacing proxy usage** + +```diff +const script = useScript('..', { + use() { return { foo: [] } } +}) + +-script.foo.push('bar') ++script.proxy.foo.push('bar') +``` + +--- + +## Tag Sorting Updated + +🚦 Impact Level: **Low** + +[Capo.js](https://rviscomi.github.io/capo.js/) sorting is now the default. You can opt-out: + +```ts +createHead({ + disableCapoSorting: true, +}) +``` + +--- + +## Default SSR Tags + +🚦 Impact Level: **Low** + +When SSR Unhead will now insert important default tags for you: + +- `` +- `` +- `` + +```ts +import { createHead } from 'unhead/server' + +// disable when creating the head instance +const head = createHead({ + disableDefaults: true, +}) +``` + +--- + +## CJS Exports Removed + +🚦 Impact Level: **Low** + +CommonJS exports have been removed in favor of ESM only. + +```diff +-const { createHead } = require('unhead/client') ++import { createHead } from 'unhead/client' +``` + +--- + +## Deprecated `@unhead/schema` + +🚦 Impact Level: **Low** + +The `@unhead/schema` package is deprecated. Import from `unhead/types` or `unhead` instead. + +```diff +-import { HeadTag } from '@unhead/schema' ++import { HeadTag } from 'unhead/types' +``` diff --git a/docs/head/1.guides/releases/v3.md b/docs/content/7.releases/1.v3.md similarity index 84% rename from docs/head/1.guides/releases/v3.md rename to docs/content/7.releases/1.v3.md index 86b3a1b5c..9edd6f8b3 100644 --- a/docs/head/1.guides/releases/v3.md +++ b/docs/content/7.releases/1.v3.md @@ -1,15 +1,15 @@ --- title: "Unhead v3" -description: "Unhead v3 release notes - streaming SSR, synchronous rendering, and significant performance improvements." +description: "Unhead v3 release notes: streaming SSR, synchronous rendering, and significant performance improvements." navigation: title: "v3" --- Unhead v3 rebuilds the rendering engine from the ground up. The motivation: **streaming SSR**. Frameworks like Nuxt, SolidStart, and SvelteKit stream HTML to the browser as data loads, but head tags were still stuck in a request/response model, resolved once and never updated. To fix this properly, we had to make rendering synchronous, pluggable, and side-effect free. The result is a faster, smaller, and more capable head manager. -## 📣 Highlights +## Highlights -### 🌊 Streaming SSR +### Streaming SSR Head tags now update dynamically as suspense boundaries resolve during streaming. As each chunk streams to the browser, new `