Installing Unhead with React
Install @unhead/react, wrap the application with UnheadProvider, and call useHead() in components. For SSR, inject the rendered tags with transformHtmlTemplate().
Use the <Head> component for JSX-style declarations or useHead() for object input. If you are migrating from react-helmet, Unhead also ships a compatibility component.
The examples follow Vite's SSR structure; the same client entry point works in a React SPA.
Demos
Install Unhead
1. Add Dependency
Install the @unhead/react dependency in your project.
pnpm add @unhead/react2. Set Up Client-Side Rendering
Create the browser instance from @unhead/react/client. In a Vite SSR app this usually belongs in entry-client.ts; in an SPA, put it in the main entry.
The value prop supplies the head instance for browser, server, and streaming providers. The browser provider also accepts the deprecated head prop for compatibility.
import { createHead, UnheadProvider } from '@unhead/react/client'
import { StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import App from './App'
import './index.css'
const head = createHead()
hydrateRoot(
document.getElementById('root') as HTMLElement,
<StrictMode>
<UnheadProvider value={head}>
<App />
</UnheadProvider>
</StrictMode>,
)
3. Set Up Server-Side Rendering
For SSR, create a fresh instance from @unhead/react/server for each request and return it with the rendered app.
import { createHead, UnheadProvider } from '@unhead/react/server'
import { StrictMode } from 'react'
import { renderToString } from 'react-dom/server'
import App from './App'
export function render(_url: string) {
const head = createHead()
const html = renderToString(
<StrictMode>
<UnheadProvider value={head}>
<App />
</UnheadProvider>
</StrictMode>,
)
return { html, head }
}
After React renders, pass the instance and complete HTML template to transformHtmlTemplate():
import { transformHtmlTemplate } from '@unhead/react/server'
// ...
// Serve HTML
app.use('*all', async (req, res) => {
try {
// ...
const rendered = await render(url)
const html = transformHtmlTemplate(
rendered.head,
template.replace(`<!--app-html-->`, rendered.html ?? '')
)
res.status(200).set({ 'Content-Type': 'text/html' }).send(html)
}
catch (e) {
// ...
}
})
// ..
4. Add Head Tags
The server head inserts three defaults:
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><html lang="en">
Set server-only defaults through init. Keeping them in the server entry also keeps them out of the client bundle.
import { createHead } from '@unhead/react/server'
export function render(_url: string) {
const head = createHead({
// change default initial lang
init: [
{
htmlAttrs: { lang: 'en' },
title: 'Default title',
titleTemplate: '%s - My Site',
},
]
})
const html = `<!-- your html -->`
return { html, head }
}
To add tags in your components, use either the <Head> component or the useHead() hook.
useHead()accepts the full typed head object and entry options.<Head>accepts native head elements as children.
import { Head, useHead } from '@unhead/react'
export default function App() {
// a. use the hook
useHead({
title: 'Example Site',
meta: [
{ name: 'description', content: 'An example page description.' }
]
})
// b. use the component
return (
<div>
<Head>
<title>Example Site</title>
<meta name="description" content="An example page description." />
</Head>
<h1>Hello World</h1>
</div>
)
}
5. Enable Auto-Imports (Optional)
unplugin-auto-import can provide the composables without explicit imports:
import { hookImports } from '@unhead/react'
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
imports: [
hookImports,
],
}),
// ...
]
})
Next Steps
Hooks and components:
For react-helmet, see the migration guide.
Optional integrations:
- Add
useSchemaOrg()for structured data - Use
useScript()for loading triggers and script lifecycle hooks