Install Unhead in TypeScript Projects
Install unhead, create a head with createHead(), and call useHead() directly. SSR uses a separate server instance and renderSSRHead().
This setup is for TypeScript applications without a framework adapter. If you use a framework, select its adapter below.
Demos
Install Unhead
Install the unhead dependency:
pnpm add unheadClient-Side Rendering
Create the browser instance from unhead/client. In a Vite SSR app this usually belongs in entry-client.ts; in an SPA, put it in the main entry.
import { createHead } from 'unhead/client'
import { setupCounter } from './counter'
import './style.css'
import './typescript.svg'
declare global {
interface Window {
__UNHEAD__: ReturnType<typeof createHead>
}
}
window.__UNHEAD__ = createHead()
setupCounter(document.querySelector('#counter') as HTMLButtonElement)
This example uses window.__UNHEAD__ to keep the first setup short. For application code, wrap the composables in your own context instead of relying on a global.
Server-Side Rendering
Somewhere in your server entry, create a server head instance.
import { createHead } from 'unhead/server'
export function render(_url: string) {
const head = createHead()
const html = `<!-- your html -->`
return { html, head }
}
After rendering the application, pass the head instance and complete template to transformHtmlTemplate():
import { transformHtmlTemplate } from 'unhead/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) {
// ...
}
})
// ..
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/server'
export function render(_url: string) {
const head = createHead({
// change default initial lang
init: [
{
title: 'Default title',
titleTemplate: '%s | My Site',
htmlAttrs: { lang: 'fr' }
},
]
})
const html = `<!-- your html -->`
return { html, head }
}
This counter creates one entry, then patches it on each click:
import { useHead } from 'unhead'
export function setupCounter(element: HTMLButtonElement) {
let counter = 0
const entry = useHead(window.__UNHEAD__, {})
const setCounter = (count: number) => {
counter = count
element.textContent = `count is ${counter}`
entry.patch({
title: counter ? `count is ${counter}` : null,
})
}
element.addEventListener('click', () => setCounter(counter + 1))
setCounter(0)
}
Next Steps
- Learn more about app context in the Wrapping Composables guide
- Consider using the Vite plugin