Reactivity in React
Pass React state directly to useHead(). A re-render patches the component's existing head entry.
React Integration
UnheadProvider places one head instance in React context. useHead() reads that instance, creates an entry, and patches it after renders.
Provide the Head Instance
Create the client instance once, then pass it to UnheadProvider:
import { createHead, UnheadProvider } from '@unhead/react/client'
// Create head instance with custom options
const head = createHead()
function App() {
return (
<UnheadProvider value={head}>
<YourApp />
</UnheadProvider>
)
}
Reactive Head Tags
Pass state values in the head object:
import { useHead } from '@unhead/react'
import { useState } from 'react'
function PageHead() {
const [title, setTitle] = useState('Welcome to My App')
// Head tags will update when title state changes
useHead({
title
})
return (
<button onClick={() => setTitle('Updated Title')}>
Update Title
</button>
)
}
Async Data
Update state when the request completes; the next render updates the entry:
import { useHead } from '@unhead/react'
import { useEffect, useState } from 'react'
function PageHead() {
const [title, setTitle] = useState('Loading...')
useEffect(() => {
async function loadData() {
const response = await fetch('/api/page')
const data = await response.json()
setTitle(data.title)
}
loadData()
}, [])
useHead({
title
})
return null
}
Group Related Tags
Keep related tags in the same entry when they come from the same data:
function ProductHead({ id }) {
const [product, setProduct] = useState({
title: 'Loading...',
description: '',
image: '/placeholder.jpg',
price: ''
})
useEffect(() => {
fetchProduct(id).then(setProduct)
}, [id])
useHead({
title: product.title,
meta: [
{ name: 'description', content: product.description },
{ property: 'og:image', content: product.image },
{ property: 'product:price', content: product.price }
]
})
return null
}
Data-Fetching Libraries
React Query and SWR require no Unhead-specific integration. Pass their result to useHead() as you would local state.
React Query
import { useQuery } from '@tanstack/react-query'
import { useHead } from '@unhead/react'
function PageHead({ id }) {
const { data = { title: 'Loading...', description: '' } } = useQuery({
queryKey: ['page', id],
queryFn: () => fetchPage(id)
})
useHead({
title: data.title,
meta: [
{ name: 'description', content: data.description }
]
})
return null
}
SWR
import { useHead } from '@unhead/react'
import useSWR from 'swr'
function PageHead({ slug }) {
const { data = { title: 'Loading...', description: '' } } = useSWR(
`/api/pages/${slug}`,
fetcher
)
useHead({
title: data.title,
meta: [
{ name: 'description', content: data.description }
]
})
return null
}
Memoizing Large Inputs
Memoize Complex Head Configurations
If building the head object is expensive, memoize it with the same dependencies as the page data:
import { useHead } from '@unhead/react'
import { useMemo } from 'react'
function SEOHead({ title, description, image }) {
const headConfig = useMemo(() => ({
title,
meta: [
{ name: 'description', content: description },
{ property: 'og:title', content: title },
{ property: 'og:description', content: description },
{ property: 'og:image', content: image }
]
}), [title, description, image])
useHead(headConfig)
return null
}
Entry Lifecycle
On the server, useHead() creates its entry during render. In the browser, it creates the entry in an effect, patches it when the input changes, and disposes it when the component unmounts.
React runs an effect's cleanup before rerunning the setup after a dependency changes and when the component unmounts. In development, Strict Mode also runs one extra setup-and-cleanup cycle; Unhead's disposal follows that documented effect lifecycle, so the rehearsal does not leave a duplicate entry behind.