--- title: "Release: Unhead v3" description: "Unhead v3 release notes: streaming SSR, a unified Vite plugin with DevTools, and rendering and bundle optimizations." navigation: title: "v3" publishedAt: "2026-04-10" updatedAt: "2026-07-20" --- Streaming SSR drove the Unhead v3 rendering rewrite. Frameworks such as Nuxt, SolidStart, and SvelteKit can stream HTML as data resolves, while earlier Unhead versions resolved head tags once per response. The v3 renderer is synchronous and supports head updates during a stream. ## 📣 Highlights ### 🌊 Streaming SSR Head tags can update as suspense boundaries resolve. When a boundary registers another entry, Unhead serializes it to a client-side queue instead of waiting for the full response. ```ts [Vue server] // 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) ``` ```ts [Vue client] // entry-client.ts import { createStreamableHead } from '@unhead/vue/stream/client' const head = createStreamableHead() if (!head) throw new Error('Unhead streaming bootstrap state is missing') app.use(head) ``` Before the main JavaScript bundle loads, a queue stub (`window.__unhead__`) collects incoming head entries. The client head instance drains that queue during initialization, then handles subsequent updates. 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 A single `@unhead/{framework}/vite` plugin replaces the old manual composition of `@unhead/addons`, a streaming plugin, and framework glue. By default, it enables tree-shaking, the `useSeoMeta` → `useHead` transform, development validation, and Vite DevTools integration. The panel appears when Vite DevTools is enabled. Inline minification and streaming SSR are available through options. ```ts [vite.config.ts] import { Unhead } from '@unhead/dynamic-import/vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue(), Unhead({ streaming: true })], }) ``` 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()` 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. ```ts useHead({ 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), [#744](https://github.com/unjs/unhead/pull/744). ### ✅ ValidatePlugin The optional `ValidatePlugin` inspects resolved head output. Its rules cover missing titles, duplicate resource hints, conflicting preload priorities, render-blocking scripts, late `` tags, excessive `fetchpriority="high"` hints, and preconnects without `crossorigin`. Migration rules detect deprecated properties (`children`, `hid`/`vmid`, and `body: true`) and missing `TemplateParamsPlugin` or `AliasSortingPlugin` registrations. The unified Vite plugin registers validation during development, and rules use ESLint-style flat config: ```ts import { ValidatePlugin } from '@unhead/dynamic-import/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 The new built-in `CanonicalPlugin` resolves relative URLs in existing canonical links and supported URL meta tags, including `og:image`, `twitter:image`, and `og:url`. For canonical links and `og:url`, it can filter query parameters, normalize trailing slashes, and strip hash fragments. It does not create a canonical link when one is missing. ```ts import { CanonicalPlugin } from '@unhead/dynamic-import/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 The optional `MinifyPlugin` minifies inline `