Unhead: Universal Head Manager
Unhead is a full-stack, framework-agnostic head manager designed to simplify and standardize how metadata (like `<title>`, `<meta>`, `<link>`) is managed in JavaScript applications. It is currently at version 3.0.4, with frequent patch releases addressing bug fixes and minor improvements, and major versions (like the recent v3.0.0) introducing significant architectural changes, such as the rebuild for streaming SSR. Its key differentiators include comprehensive framework agnosticism, reactive head management, robust server-side rendering support with a focus on streaming capabilities, SEO-friendliness, type safety via TypeScript, and a lightweight, tree-shakable design optimized for performance with minimal runtime overhead. It integrates seamlessly with popular frameworks through dedicated packages like `@unhead/vue` and `@unhead/react`.
Common errors
-
TypeError: head.render is not a function
cause Attempting to call `render()` on a `head` instance created with v3, where `render` was renamed to `renderSync`.fixChange `head.render()` to `head.renderSync()` for synchronous SSR rendering. -
Error [ERR_REQUIRE_ESM]: require() of ES Module ... unhead/index.mjs not supported.
cause Attempting to import `unhead` using CommonJS `require()` syntax in an environment where `unhead` is distributed as an ES Module.fixUpdate your import statements to use ES Module syntax: `import { createHead, useHead } from 'unhead'`. -
Property 'title' does not exist on type 'HeadEntryOptions' or similar TypeScript errors when defining head elements.
cause Type widening in older Unhead versions or incorrect type definitions being passed, preventing proper type inference for specific meta attributes or elements. Might also occur if using a custom `rel` or `type` without proper type narrowing escape hatches.fixUpgrade Unhead to the latest v3 patch version (e.g., v3.0.4 or later) as recent fixes address `readonly` inputs and string widening issues. Ensure custom `rel` or `type` attributes are correctly typed as `string & {}` or use `as const` for literals to prevent widening. -
Vite dev mode issues with head tags not updating correctly or SSR hydration mismatches.
cause In `v3.0.3`, a bug was fixed where `SSRStaticReplace` was improperly disabled in Vite dev mode, leading to potential inconsistencies between server-rendered and client-hydrated head content.fixEnsure you are using `unhead` version 3.0.3 or higher if you are experiencing inconsistent head tag updates or hydration issues in a Vite development environment.
Warnings
- breaking Unhead v3.0.0 introduces a complete rebuild of the rendering engine to support streaming SSR. This change makes rendering synchronous, pluggable, and side-effect free. Existing v2 code relying on asynchronous rendering or specific internal behaviors will break. The core API for `render` became `renderSync`.
- gotcha A security vulnerability (XSS bypass via attribute name injection) was fixed in v2.1.11 when using `useHeadSafe`. This could allow malicious script injection if `useHeadSafe` was used with untrusted user input.
- gotcha Unhead v2.1.12 included a fix for prototype pollution. While not explicitly detailed as an exploit, prototype pollution vulnerabilities can lead to various security issues, including arbitrary code execution or denial of service.
- gotcha Since v3, Unhead is primarily an ESM-first package. Attempting to use `require()` for imports will lead to errors in most modern Node.js environments unless specific transpilation or module resolution configurations are in place.
- deprecated In v3, the main `unhead` package for SSR context changed its export path. While `v3.0.3` restored some legacy exports for migration, relying on `createHead` from the root `unhead` package for SSR is generally incorrect.
Install
-
npm install unhead -
yarn add unhead -
pnpm add unhead
Imports
- createHead
const { createHead } = require('unhead')import { createHead } from 'unhead' - useHead
import useHead from 'unhead/vue'
import { useHead } from 'unhead' - createHead (SSR)
import { createHead } from 'unhead'import { createHead } from 'unhead/server'
Quickstart
import { createHead, useHead } from 'unhead';
// 1. Create a head instance. For SSR, use `createHead` from 'unhead/server'.
const head = createHead();
// 2. Add reactive head entries using `useHead`.
// This example updates the title and adds a meta description.
useHead({
title: 'My Dynamic App Title',
meta: [
{ name: 'description', content: 'This is a dynamic description for my awesome application.' },
{ property: 'og:title', content: 'Open Graph Title' }
],
link: [
{ rel: 'canonical', href: 'https://example.com/my-page' }
],
script: [
{ src: 'https://unpkg.com/some-script.js', defer: true, body: true }
]
}, { head });
// 3. For SSR, render the head tags.
// In a real SSR environment, you'd call this after all components have rendered.
const { headTags, bodyTags } = head.renderSync();
console.log('Generated Head Tags:\n', headTags);
console.log('Generated Body Tags (scripts in body):\n', bodyTags);
// Output example (simplified):
// <title>My Dynamic App Title</title>
// <meta name="description" content="This is a dynamic description for my awesome application.">
// ...
// <script src="https://unpkg.com/some-script.js" defer></script>