Reactivity in Solid.js
Create one entry with useHead(), read signals in createEffect(), and patch that entry when they change.
useHead() when tags must appear in the server response, then use an effect to patch later client-side changes.Solid Integration
Unhead for Solid.js uses Solid context for the head instance and Solid's onCleanup() lifecycle to dispose entries. To update an entry when a signal changes, track the signal in createEffect() and call patch().
Provide the Head Instance
Provide one head instance at the application root:
import { createHead, UnheadContext } from '@unhead/solid-js/client'
import { render } from 'solid-js/web'
import App from './App'
const head = createHead()
render(() => (
<UnheadContext.Provider value={head}>
<App />
</UnheadContext.Provider>
), document.getElementById('root'))
Signals
Create the head entry once, then patch it from an effect that reads the signal:
import { useHead } from '@unhead/solid-js'
import { createEffect, createSignal } from 'solid-js'
function PageHead() {
const [title, setTitle] = createSignal('Welcome to My App')
const entry = useHead({ title: title() })
createEffect(() => {
entry.patch({ title: title() })
})
return (
<button onClick={() => setTitle('Updated Title')}>
Update Title
</button>
)
}
Updating Several Values
Read every dependency inside the effect and patch the complete entry:
import { useHead } from '@unhead/solid-js'
import { createEffect, createSignal } from 'solid-js'
function PageHead() {
const [title, setTitle] = createSignal('Welcome to My App')
const [description, setDescription] = createSignal('My site description')
const entry = useHead({
title: title(),
meta: [{ name: 'description', content: description() }],
})
createEffect(() => {
entry.patch({
title: title(),
meta: [
{ name: 'description', content: description() }
]
})
})
return (
<>
<button onClick={() => setTitle('Updated Title')}>Update Title</button>
<button onClick={() => setDescription('New description')}>Update Description</button>
</>
)
}
Async Data
Patch the entry when a resource settles:
import { useHead } from '@unhead/solid-js'
import { createEffect, createResource } from 'solid-js'
async function fetchPageData(id) {
const response = await fetch(`/api/page/${id}`)
return response.json()
}
function PageHead({ id }) {
const [pageData] = createResource(() => id, fetchPageData)
const entry = useHead()
createEffect(() => {
entry.patch({
title: pageData()?.title || 'Loading...',
meta: [
{
name: 'description',
content: pageData()?.description || 'Loading page content...'
}
]
})
})
return null
}
Group Related Tags
Put tags derived from the same resource in one entry:
import { useHead } from '@unhead/solid-js'
import { createEffect, createResource } from 'solid-js'
async function fetchProduct(id) {
const response = await fetch(`/api/products/${id}`)
return response.json()
}
function ProductHead({ id }) {
const [product] = createResource(() => id, fetchProduct)
const entry = useHead()
createEffect(() => {
entry.patch({
title: product()?.title || 'Loading Product...',
meta: [
{ name: 'description', content: product()?.description || '' },
{ property: 'og:image', content: product()?.image || '/placeholder.jpg' },
{ property: 'product:price', content: product()?.price || '' }
]
})
})
return null
}
Reusable Head Components
A small wrapper component can own repeated SEO tags:
import { useHead } from '@unhead/solid-js'
import { createEffect, mergeProps } from 'solid-js'
function SEOHead(props) {
const merged = mergeProps({
title: 'Default Title',
description: 'Default description',
ogImage: '/default-og.jpg'
}, props)
const entry = useHead()
createEffect(() => {
entry.patch({
title: merged.title,
meta: [
{ name: 'description', content: merged.description },
{ property: 'og:title', content: merged.title },
{ property: 'og:description', content: merged.description },
{ property: 'og:image', content: merged.ogImage }
]
})
})
return null
}
// Usage
function HomePage() {
return (
<div>
<SEOHead
title="Home Page"
description="Welcome to our website"
/>
{/* Page content */}
</div>
)
}
Conditional Tags
Patch the complete entry when a condition changes:
import { useHead } from '@unhead/solid-js'
import { createEffect, createSignal, Show } from 'solid-js'
function DynamicHead() {
const [isLoggedIn, setIsLoggedIn] = createSignal(false)
const [user, setUser] = createSignal<{ name: string, id: string } | null>(null)
const entry = useHead()
// Simulate login
const login = () => {
setUser({ name: 'John Doe', id: '123' })
setIsLoggedIn(true)
}
createEffect(() => {
if (isLoggedIn()) {
entry.patch({
title: `Dashboard - ${user()!.name}`,
meta: [
{ name: 'description', content: 'Your personal dashboard' }
]
})
}
else {
entry.patch({
title: 'Login',
meta: [
{ name: 'description', content: 'Login to access your dashboard' },
{ name: 'robots', content: 'noindex' }
]
})
}
})
return (
<div>
<Show when={isLoggedIn()} fallback={<button onClick={login}>Login</button>}>
<h1>
Welcome,
{user()!.name}
!
</h1>
</Show>
</div>
)
}
Entry Cleanup
The adapter registers each entry with Solid's current owner. onCleanup() disposes it with the component.