Index of every page on this site: llms.txt
React
Core Concepts

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
}

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.

Did this page help you?