Release: 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)

[User Guides](https://unhead.unjs.io/docs/typescript/head/guides/get-started/overview)

[API](https://unhead.unjs.io/docs/typescript/head/api/get-started/overview)

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

TypeScript

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

v3 (stable)

Head

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

- Releases
  - [v3](https://unhead.unjs.io/docs/typescript/releases/v3)
  - [v2](https://unhead.unjs.io/docs/typescript/releases/v2)
- Migration Guide
  - [v3](https://unhead.unjs.io/docs/typescript/migration-guide/v3)
  - [v2](https://unhead.unjs.io/docs/typescript/migration-guide/v2)

Releases

# Release: Unhead v3

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

Last updated Apr 10, 2026 by [Harlan Wilton](https://github.com/harlan-zw) in [fix: better type narrowing escape hatches for custom rel/type (#735)](https://github.com/unjs/unhead/pull/735).

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/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/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).

### [🛠️ Unified Vite Plugin + DevTools](#️-unified-vite-plugin-devtools)

A single `@unhead/{framework}/vite` plugin replaces the old manual composition of `@unhead/addons` + streaming plugin + framework glue. One import, one call, and you get tree-shaking, `useSeoMeta` → `useHead` transform, inline minification, streaming SSR, dev-mode `ValidatePlugin` auto-injection, and **Vite DevTools** integration.

vite.config.ts

```
import { Unhead } from 'unhead/vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue(), Unhead()],
})
```

The DevTools panel surfaces live head state during development: every `useHead()` / `useSeoMeta()` call with its source file and line number, resolved tags, SEO overview (title, description, canonical, Open Graph), `useScript()` load status, active plugins, template params, and warnings from the Validate plugin. Source tracing lets you click through from any tag back to the exact line that created it.

Available for Vue, React, Svelte, Solid, and vanilla via `@unhead/bundler/vite` (the renamed `@unhead/addons` package; the old name still works with a deprecation warning).

See PRs [#726](https://github.com/unjs/unhead/pull/726), [#733](https://github.com/unjs/unhead/pull/733), [#731](https://github.com/unjs/unhead/pull/731).

### [🎯 `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, render-blocking scripts, late `<meta charset>`, too many `fetchpriority="high"` hints, preconnect without `crossorigin`, and more. Includes **v2 migration rules** that detect deprecated property names (`children`, `hid`/`vmid`, `body: true`), missing `TemplateParamsPlugin`, and missing `AliasSortingPlugin`, all of which cause silent breakage on upgrade. Auto-injected in dev by the unified Vite plugin so warnings surface in the browser console without manual setup. 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), [#716](https://github.com/unjs/unhead/pull/716), [#722](https://github.com/unjs/unhead/pull/722), [#725](https://github.com/unjs/unhead/pull/725), [#732](https://github.com/unjs/unhead/pull/732).

### [🔗 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`. Includes query parameter filtering (strips tracking params like `utm_source`, `fbclid`, `gclid` by default), trailing slash normalization, and automatic hash fragment stripping. Essential for SEO and social sharing.

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

createHead({
  plugins: [
    CanonicalPlugin({
      canonicalHost: 'https://mysite.com',
      trailingSlash: true,
      queryWhitelist: ['page', 'sort'],
    })
  ]
})
```

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

### [🗜️ MinifyPlugin](#️-minifyplugin)

New optional `MinifyPlugin` that minifies inline `<script>` and `<style>` tag content during SSR. Uses lightweight pure-JS minifiers with zero native dependencies, safe for edge and serverless runtimes. A companion build-time transform (`MinifyTransform` in `@unhead/bundler`) pre-minifies static `innerHTML` literals at compile time. Standalone utilities (`minifyJS`, `minifyCSS`, `minifyJSON`) are also available via `unhead/minify`.

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

createHead({
  plugins: [MinifyPlugin()]
})
```

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

## [📦 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/dynamic-import | 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)

- `renderDOMHead()` / `renderSSRHead()` are now fully synchronous, single-pass via a composable `resolveTags()` pipeline; the head instance exposes a pluggable `render()` function for framework integrations ([#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))
- `@unhead/react/helmet` drop-in compat export for users migrating from `react-helmet` ([#719](https://github.com/unjs/unhead/pull/719))
- `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))
- `onRendered` callback option on `useHead()` for synchronizing with DOM head updates ([#712](https://github.com/unjs/unhead/pull/712))
- `tagWeight` option on `createHead()` to override default CAPO tag weight function ([#716](https://github.com/unjs/unhead/pull/716))

## [🐛 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/dynamic-import/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))
- Schema.org: normalize `target` to array before merging `potentialAction` ([#709](https://github.com/unjs/unhead/pull/709))
- Avoid mutating cached `titleTemplate` tag in `resolveTitleTemplate` ([#715](https://github.com/unjs/unhead/pull/715))

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

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

### [Summary](#summary)

| Old | New |
| --- | --- |
| `children` | `innerHTML` |
| `hid` / `vmid` | `key` |
| `body: true` | `tagPosition: 'bodyClose'` |
| `useServerHead` / `useServerSeoMeta` | `useHead` / `useSeoMeta` |
| `createHeadCore` | `createUnhead` |
| `@unhead/dynamic-import/legacy` | `@unhead/dynamic-import/client` or `@unhead/dynamic-import/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
- `@unhead/addons` renamed to `@unhead/bundler`; framework Vite plugins now use a named `Unhead` export instead of a default export
- `Link` / `Script` unions are strict: use the new `defineLink` / `defineScript` helpers for custom `rel` / `type` values, and meta `content` is now required (use `content: null` to remove)

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

[Markdown For LLMs](https://raw.githubusercontent.com/unjs/unhead/refs/heads/main/docs/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/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/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/)

### Head Management

- [Getting Started](https://unhead.unjs.io/docs/typescript/head/guides/get-started/overview)
- [useHead](https://unhead.unjs.io/docs/typescript/head/api/composables/use-head)
- [useSeoMeta](https://unhead.unjs.io/docs/typescript/head/api/composables/use-seo-meta)
- [useHeadSafe](https://unhead.unjs.io/docs/typescript/head/api/composables/use-head-safe)
- [useScript](https://unhead.unjs.io/docs/typescript/head/api/composables/use-script)

### Schema.org

- [Getting Started](https://unhead.unjs.io/docs/typescript/schema-org/guides/get-started/overview)
- [useSchemaOrg](https://unhead.unjs.io/docs/typescript/schema-org/api/composables/use-schema-org)
- [Nodes](https://unhead.unjs.io/docs/typescript/schema-org/guides/core-concepts/nodes)
- [Recipes](https://unhead.unjs.io/docs/typescript/schema-org/guides/recipes/identity)

### Guides

- [Titles](https://unhead.unjs.io/docs/typescript/head/guides/core-concepts/titles)
- [Streaming SSR](https://unhead.unjs.io/docs/typescript/head/guides/core-concepts/streaming)
- [DOM Events](https://unhead.unjs.io/docs/typescript/head/guides/core-concepts/dom-event-handling)
- [Plugins](https://unhead.unjs.io/docs/typescript/head/guides/plugins/template-params)

### 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)
- [Capo.js Analyzer](https://unhead.unjs.io/tools/capo-analyzer)

### Articles

- [What is Capo.js?](https://unhead.unjs.io/learn/guides/what-is-capo)

### Research

- [State of <head> in 2026](https://unhead.unjs.io/learn/research/state-of-head-2026)
- [Streaming Head Performance](https://unhead.unjs.io/learn/research/streaming-head-performance)
- [Capo.js Performance Research](https://unhead.unjs.io/learn/research/capo-performance-research)

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