Index of every page on this site: llms.txt
TypeScript
Releases

Release: Unhead v2

Unhead v2 adds adapters for React, Svelte, Solid.js, and Angular alongside the existing Vue integration. It also rewrites the core rendering path.

Highlights

  • Multi-framework Support: Added support for React, Svelte, Solid.js, and Angular
  • Rendering: A rewritten core and cached tag resolution
  • Capo.js Integration: Automatic tag ordering based on Capo.js recommendations
  • Dependencies: A smaller runtime dependency set
  • Documentation: New framework-specific installation and reactivity guides

State of head management

JavaScript frameworks need to manage document metadata outside the application's DOM mount point, on both the server and client:

<!DOCTYPE html>
<html>
  <head>
    <!-- Managed during server rendering and client navigation. -->
  </head>
  <body class="page">
    <div id="your-javascript-framework"></div>
    <script src="/your-javascript-framework.js"></script>
  </body>
</html>

Frameworks commonly expose a component or provider that collects these tags during rendering:

ComponentFoo
<HeadProvider>
  <title>My Page</title>
  <meta name="description" content="My page description" />
  <link rel="canonical" href="https://example.com/my-page" />
</HeadProvider>

Examples include <Helmet>, <Head>, <svelte:head>, and granular components such as <Title>, <Meta>, and <Link>.

Ecosystem

Framework ecosystems supplied many of the early solutions. Vue had Vue Meta and VueUse Head, while React applications often used React Helmet.

React v19 added native metadata elements. Angular Router also exposes a title property on routes.

A quick recap on Unhead

Unhead started as @vueuse/head and later moved to UnJS. The npm registry recorded 948,265 downloads of unhead in the week before this release, an average of more than 135,000 per day.

Its main APIs are useHead() for document tags and useSeoMeta() for typed SEO metadata.

v2 Release

For the original scope and discussion, see the v2 roadmap.

New Framework Support

Unhead v2 adds React, Svelte, Solid.js, and Angular adapters with framework-specific reactivity and lifecycle handling.

import { useHead } from 'unhead'

useHead(unheadInstance, {
  title: '๐Ÿ‘‹ @FRAMEWORK_NAME@'
})

Each adapter exposes the same core tag APIs through its framework's context and lifecycle primitives.

โšก Runtime Performance Improvements

This release rewrites much of the core to reduce rendering work and improve tree-shaking.

Rewritten Core: The core rewrite reduces work in the rendering path.

Cached tag resolution: Tags are now cached between DOM renders, reducing the work performed while switching pages.

Leaner Core: Code changes and opt-in features reduce the client and server bundle surface. The original release benchmarks and comparison environment were not retained, so exact historical timing and size claims have been omitted here.

00.1ms0.2ms0.3ms0.4ms0.34ms0.20ms+51%SSR TimeMedium Site0.43ms0.22ms+64%Page Changes10x useHead()13.6kB10.7kB-21%Client SizeMinimal Build10.3kB8.0kB-22%Server SizeMinimal BuildBeforeAfter

Capo.js Sorting

Unhead v2 applies Capo.js sorting to tags by default. This places resource hints, stylesheets, synchronous scripts, and metadata in Capo.js's recommended order.

<head>
<script defer src="defer-script.js"></script>
<script src="sync-script.js"></script>
<style>.sync-style { color: red }</style>
<link rel="modulepreload" href="modulepreload.js">
<script src="async-script.js" async></script>
<link rel="preload" href="preload.js">
<link rel="stylesheet" href="sync-styles.css">
<title>title</title>
<link rel="preconnect" href="https://example.com">
<link rel="dns-prefetch" href="https://example.com">
<link rel="prefetch" href="https://example.com">
<link rel="prerender" href="https://example.com">
<meta name="description" content="description">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

๐Ÿ“ฆ Bundle Improvements

Single dependency: Unhead relies on hookable for pluggability.

ESM and dropped workspace packages: Only ESM is now published, and workspace packages are deprecated in favor of subpath exports.

๐Ÿ”„ Upgrading to v2

Unhead provides a legacy subpath build for staged migrations.

See the Migrate to v2 guide.

Roadmap at Release

At release, the roadmap included validation, higher-level script integrations, and Open Graph image generation.

Short Term: Improved Tag Validation

Some tag attributes depend on another attribute. For example, a <link rel="preload"> needs an as value.

At the time of the v2 release, Unhead's types did not enforce every such combination.

The roadmap considered three ways to catch invalid tag combinations:

  • Improve the type system for optionally required attributes
  • Implement ESLint rules to catch anything the types haven't
  • Use a runtime plugin in development to validate resolved tags

Medium Term: Third-Party Scripts

The roadmap proposed service-specific wrappers around useScript(), such as useGoogleAnalytics() and useFacebookPixel().

These wrappers would connect script loading to framework lifecycles and expose typed service APIs.

const { proxy } = useGoogleAnalytics({
  id: 'UA-123456789',
})

proxy.gtag('page_view', {
  page_path: '/my-page',
})

Exploratory: OG Image

The roadmap also explored turning HTML templates into Open Graph images from a composable.

useOgImage('<div class="bg-red-500">my image :)</div>')

๐Ÿ™ Thank You

Thank you to everyone who contributed code, tests, and documentation.

Did this page help you?