🛠️ Unhead CLI
To assist with migrations and overall DX a CLI has been introduced: @unhead/cli.
npx -y @unhead/cli It lets you do the following:
audit Lint your codebase for unhead misuse, type-narrowing issues, and SEO/perf foot-guns.
migrate Apply autofixes for v2-to-v3 migration: rewrite deprecated props and wrap tag literals in defineX helpers.
validate-html Run the runtime ValidatePlugin over prerendered HTML files (e.g. dist/, .output/, build/).
validate-url Fetch a rendered URL and run unhead\'s SEO/perf validation rules over its <head>. For example, try running audit on your own project for hints on how to improve your SEO.
✔️ Unhead ESLint
Knowing that your useHead() and useSeoMeta() code is right while your coding is important. While type-narrowing solves many broken cases, we introduce an ESLint plugin to help catch anything that the typechecker can't catch.
These rules are shared from the runtime ValidatePlugin
# flat-config ESLint plugin with v2→v3 migration autofixes
npm i -D @unhead/eslint-plugin```ts [eslint.config.ts]
import { configs } from '@unhead/eslint-plugin'
export default [
configs.recommended,
]🌊 Streaming SSR non-Vite support
The streaming plugin lived only at unhead/stream/vite previously, leaving non-Vite users with no way to wire the bootstrap. The plugin is now a bundler-agnostic unplugin factory with first-class webpack and Vite entries, and the framework packages compose it behind Unhead({ streaming: true }).
// vite.config.ts
import { Unhead } from '@unhead/vue/vite'
export default { plugins: [vue(), Unhead({ streaming: true })] }
// webpack.config.ts
import { Unhead } from '@unhead/vue/bundler'
export default { plugins: [...Unhead({ streaming: true }).webpack()] }Streaming also gains a nonce option (forwarded on every injected <script> for CSP support), a fixed async mode for production Vite builds (the IIFE is now emitted via this.emitFile() so the script src references a real hashed asset), a dev-mode warning when the client IIFE runs against an empty server queue, and a shared StreamingGlobal type so the server bootstrap, client, and injected IIFE agree on the shape of window.__unhead__. Default mode changed from async to inline for smaller TTFB.
Changelog
🚀 Features
- cli,eslint-plugin:
- Add @unhead/cli and @unhead/eslint-plugin - by @harlan-zw in #755 (9e300)
- stream:
- Unify framework plugins as Unhead({ streaming: true }) per bundler - by @harlan-zw (63c03)
- Webpack plugin + bundler-agnostic factory - by @harlan-zw in #751 (3ed06)
🐞 Bug Fixes
- stream:
- Add missing webpack/rspack/rollup plugin entries - by @harlan-zw (7feae)
- Address coderabbit review comments - by @harlan-zw (180f4)
- Restore unhead/stream/vite as deprecation stub + re-add moduleType - by @harlan-zw (feee2)
- vue:
- Batch streamed head updates via self-deleting inline scripts - by @harlan-zw in #752 (40f77)