Unhead v3 · Unhead

[Unhead Home](https://unhead.unjs.io/ "Home")

- [Docs](https://unhead.unjs.io/docs/typescript/head/guides/get-started/overview)
- [Tools](https://unhead.unjs.io/tools)
- [Learn](https://unhead.unjs.io/learn/guides/what-is-capo)

[Releases](https://unhead.unjs.io/releases)

Search…```k`` /`

[Unhead on GitHub](https://github.com/unjs/unhead)

TypeScript

- [Switch to TypeScript](https://unhead.unjs.io/docs/typescript/content/releases/v3)
- [Switch to Vue](https://unhead.unjs.io/docs/vue/content/releases/v3)
- [Switch to React](https://unhead.unjs.io/docs/react/content/releases/v3)
- [Switch to Svelte](https://unhead.unjs.io/docs/svelte/content/releases/v3)
- [Switch to Solid.js](https://unhead.unjs.io/docs/solid-js/content/releases/v3)
- [Switch to Angular](https://unhead.unjs.io/docs/angular/content/releases/v3)
- [Switch to Nuxt](https://unhead.unjs.io/docs/nuxt/content/releases/v3)

v3 (beta)

You're viewing **Unhead v3 beta** documentation.

Head

- [Discord Support](https://discord.com/invite/275MBUBvgP)
- [TypeScript Playground](https://stackblitz.com/edit/github-hhxywsb5)

- [v3](https://unhead.unjs.io/docs/content/releases/v3)
- [v2](https://unhead.unjs.io/docs/content/releases/v2)

Releases

# Unhead v3

[Copy for LLMs](https://raw.githubusercontent.com/unjs/unhead/refs/heads/main/docs/content/7.releases/1.v3.md)

Last updated Apr 7, 2026 by [Harlan Wilton](https://github.com/harlan-zw) in [feat: v2 migration rules in ValidatePlugin + DOM hook deprecations (#722)](https://github.com/unjs/unhead/pull/722).

On this page

- [Highlights](#highlights)
- [Performance](#performance)
- [Schema.org](#schemaorg)
- [Other Changes](#other-changes)
- [Bug Fixes](#bug-fixes)
- [Breaking Changes](#breaking-changes)

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 `<title>`, `<meta>`, and `<link>` tags are pushed to a client-side queue and applied to the DOM. No waiting for the full page to load.

```
// entry-server.ts
import { createStreamableHead } from '@unhead/vue/stream/server'

const { head, wrapStream } = createStreamableHead()
app.use(head)

// wraps the Vue stream, injecting head updates as chunks resolve
return wrapStream(renderToWebStream(app), template)
```

```
// entry-client.ts
import { createStreamableHead } from '@unhead/vue/stream/client'

const head = createStreamableHead()
app.use(head)
```

Under the hood: a queue stub (`window.__unhead__`) collects head entries as they stream in before the main JS bundle loads. Once the client head instance initializes, it processes the queue and takes over. No entries are ever lost regardless of timing.

Streaming is supported for Vue, React, Solid.js, Svelte, and vanilla TypeScript. See [PR #537](https://github.com/unjs/unhead/pull/537).

### [Synchronous Rendering](#synchronous-rendering)

Both `renderDOMHead()` and `renderSSRHead()` are now **fully synchronous**. The async tag resolution pipeline has been replaced with a composable `resolveTags()` that resolves in a single pass, eliminating race conditions, unnecessary microtask scheduling, and `await` overhead.

```
- await renderDOMHead(head, { document })
+ renderDOMHead(head, { document })
```

The head instance now exposes a pluggable `render()` function, so framework integrations can swap in their own rendering pipeline (DOM, SSR, or streaming) without modifying core.

See PRs [#619](https://github.com/unjs/unhead/pull/619), [#622](https://github.com/unjs/unhead/pull/622), [#628](https://github.com/unjs/unhead/pull/628), [#629](https://github.com/unjs/unhead/pull/629), [#630](https://github.com/unjs/unhead/pull/630).

### [`useHead()` Type Narrowing](#usehead-type-narrowing)

`useHead()` now narrows types based on input. Link, script, and meta tags resolve to specific subtypes instead of a generic union, so you get precise autocomplete and type errors when something is wrong.

```
useHead(unheadInstance, {
  link: [
    // Narrows to StylesheetLink: requires href, offers media, integrity, etc.
    { rel: 'stylesheet', href: '/styles.css' },
    // Narrows to PreloadLink: requires as attribute
    { rel: 'preload', as: 'font', href: '/font.woff2', crossorigin: 'anonymous' },
  ],
  script: [
    // Narrows to ModuleScript
    { src: '/app.mjs', type: 'module' },
    // Narrows to JsonLdScript
    { type: 'application/ld+json', innerHTML: '{}' },
  ],
})
```

See [PR #627](https://github.com/unjs/unhead/pull/627), [#665](https://github.com/unjs/unhead/pull/665).

### [ValidatePlugin](#validateplugin)

New optional `ValidatePlugin` that inspects resolved head output and warns about common mistakes: missing titles, duplicate meta tags, contradictory preload priorities, and more. Fully tree-shakeable. Rules use ESLint-style flat config:

```
import { ValidatePlugin } from 'unhead/plugins'

createHead({
  plugins: [
    ValidatePlugin({
      rules: {
        'missing-description': 'off',
      }
    })
  ]
})
```

See PRs [#690](https://github.com/unjs/unhead/pull/690), [#691](https://github.com/unjs/unhead/pull/691).

### [Canonical Plugin](#canonical-plugin)

New built-in `CanonicalPlugin` that auto-generates `<link rel="canonical">` tags and resolves relative URLs to absolute in `og:image`, `twitter:image`, and `og:url`. Essential for SEO and social sharing.

```
import { CanonicalPlugin } from 'unhead/plugins'

createHead({
  plugins: [
    CanonicalPlugin({ canonicalHost: 'https://mysite.com' })
  ]
})
```

See [PR #492](https://github.com/unjs/unhead/pull/492).

## [Performance](#performance)

| Build | v3 size | v2 size | Delta gz |
| --- | --- | --- | --- |
| client | 10254 | 11513 | **-534 (-11.2%)** |
| server | 9894 | 10361 | -194 (-4.6%) |
| vueClient | 11323 | 12567 | -533 (-10.2%) |
| vueServer | 10849 | 11312 | -191 (-4.1%) |

| Benchmark | v2 mean | v3 mean | Delta |
| --- | --- | --- | --- |
| @unhead/vue | 0.106ms | 0.072ms | **32% faster** |
| core | 0.088ms | 0.073ms | **17% faster** |

Key optimizations:

- Client-only CAPO sorting ([#626](https://github.com/unjs/unhead/pull/626))
- Pure, tree-shakeable core with no side effects ([#632](https://github.com/unjs/unhead/pull/632))
- Minified internal DOM state properties ([#635](https://github.com/unjs/unhead/pull/635))
- Migrated unplugins from `estree-walker`/`acorn-loose` to `oxc-walker` ([#663](https://github.com/unjs/unhead/pull/663))
- Walker-based `transformHtmlTemplate` ([#581](https://github.com/unjs/unhead/pull/581))
- `TemplateParamsPlugin` and `AliasSortingPlugin` made opt-in for smaller bundles ([#493](https://github.com/unjs/unhead/pull/493), [#494](https://github.com/unjs/unhead/pull/494))

## [Schema.org](#schemaorg)

- **12 new nodes**: `Dataset`, `MusicAlbum`, `MusicGroup`, `MusicPlaylist`, `MusicRecording`, `PodcastEpisode`, `PodcastSeason`, `PodcastSeries`, `Service`, `TVEpisode`, `TVSeason`, `TVSeries` ([#612](https://github.com/unjs/unhead/pull/612))
- **Graph resolution rewrite** for correctness and performance ([#616](https://github.com/unjs/unhead/pull/616))
- **Removed `ohash` and `defu` dependencies** ([#605](https://github.com/unjs/unhead/pull/605))

## [Other Changes](#other-changes)

- `useHeadSafe()` now whitelists CSS styles ([#491](https://github.com/unjs/unhead/pull/491))
- Support for `blocking` attribute on scripts and stylesheets ([#489](https://github.com/unjs/unhead/pull/489))
- `useScript()` consolidated back into core, legacy support dropped ([#498](https://github.com/unjs/unhead/pull/498))
- `fediverse:creator` meta tag support ([#703](https://github.com/unjs/unhead/pull/703))
- Switched from `hookable` to lighter `HookableCore` with sync-only hooks ([#631](https://github.com/unjs/unhead/pull/631))
- Deprecation warnings added to aliased packages (`@unhead/schema`, `@unhead/shared`) ([#678](https://github.com/unjs/unhead/pull/678))
- `templateParams` extensible via module augmentation ([#679](https://github.com/unjs/unhead/pull/679))
- Respect user-provided `twitter:card` in `InferSeoMetaPlugin` ([#681](https://github.com/unjs/unhead/pull/681))
- Enforce `as` attribute for preload links ([#683](https://github.com/unjs/unhead/pull/683))

## [Bug Fixes](#bug-fixes)

- Hydration race condition with deferred patches ([#634](https://github.com/unjs/unhead/pull/634))
- Process pending patches even when dirty is false ([#636](https://github.com/unjs/unhead/pull/636))
- Deduplicate matching tags inside same render cycle ([#668](https://github.com/unjs/unhead/pull/668))
- Dedupe `<link rel="alternate">` correctly ([#655](https://github.com/unjs/unhead/pull/655), [#656](https://github.com/unjs/unhead/pull/656), [#658](https://github.com/unjs/unhead/pull/658))
- React: dispose head entries on unmount in StrictMode ([#664](https://github.com/unjs/unhead/pull/664))
- React: force invalidation on entry disposal ([#559](https://github.com/unjs/unhead/pull/559))
- Vue: support computed getter trigger ([#638](https://github.com/unjs/unhead/pull/638))
- Vue: expose `@unhead/vue/stream/iife` with correct types ([#707](https://github.com/unjs/unhead/pull/707))
- Scripts: prevent scope disposal from aborting unrelated trigger ([#660](https://github.com/unjs/unhead/pull/660))
- Schema.org: allow `null` to opt out of default values ([#680](https://github.com/unjs/unhead/pull/680))

## [Breaking Changes](#breaking-changes)

For the full migration guide, see [Migrate to v3](https://unhead.unjs.io/docs/typescript/content/migration-guide/v3).

### [Summary](#summary)

| Old | New |
| --- | --- |
| `children` | `innerHTML` |
| `hid` / `vmid` | `key` |
| `body: true` | `tagPosition: 'bodyClose'` |
| `useServerHead` / `useServerSeoMeta` | `useHead` / `useSeoMeta` |
| `createHeadCore` | `createUnhead` |
| `@unhead/vue/legacy` | `@unhead/vue/client` or `@unhead/vue/server` |
| `mode` option on entries | Use client/server `createHead` imports |

- `renderDOMHead()` and `renderSSRHead()` are now synchronous (remove `await`)
- CJS removed, all packages are ESM-only
- `TemplateParamsPlugin` and `AliasSortingPlugin` are no longer included by default
- `init`, `dom:renderTag`, `dom:rendered` hooks removed; `dom:beforeRender` is now synchronous

[Edit this page](https://github.com/unjs/unhead/edit/main/docs/content/7.releases/1.v3.md)

[Markdown For LLMs](https://raw.githubusercontent.com/unjs/unhead/refs/heads/main/docs/content/7.releases/1.v3.md)

Did this page help you?

[v2 Migrate from Unhead v1 to v2. Covers subpath exports, removed implicit context, opt-in plugins, and more.](https://unhead.unjs.io/docs/content/migration-guide/v2) [v2 Unhead v2 is here! With first-class support for all major frameworks, a complete core rewrite, and a focus on performance, Unhead is the ultimate <head> manager.](https://unhead.unjs.io/docs/content/releases/v2)

On this page

- [Highlights](#highlights)
- [Performance](#performance)
- [Schema.org](#schemaorg)
- [Other Changes](#other-changes)
- [Bug Fixes](#bug-fixes)
- [Breaking Changes](#breaking-changes)

[GitHub](https://github.com/unjs/unhead) [ Discord](https://discord.com/invite/275MBUBvgP)

[ /llms.txt](https://unhead.unjs.io/llms.txt)

[Part of the UnJS ecosystem](https://unjs.io/)

### Articles

- [Announcing Unhead v2](https://unhead.unjs.io/releases/v2)

### Tools

- [Meta Tag Generator](https://unhead.unjs.io/tools/meta-tag-generator)
- [OG Image Generator](https://unhead.unjs.io/tools/og-image-generator)
- [Schema.org Generator](https://unhead.unjs.io/tools/schema-generator)

### Head

- [Overview](https://unhead.unjs.io/docs/typescript/head/guides/get-started/overview)
- [Introduction to Unhead](https://unhead.unjs.io/docs/typescript/head/guides/get-started/intro-to-unhead)
- [Starter Recipes](https://unhead.unjs.io/docs/typescript/head/guides/get-started/starter-recipes)

### Schema.org

- [Introduction](https://unhead.unjs.io/docs/typescript/schema-org/guides/get-started/overview)

Copyright © 2025-2026 Harlan Wilton - [MIT License](https://github.com/unjs/unhead/blob/main/license)