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

Reactivity in Solid.js

Create one entry with useHead(), read signals in createEffect(), and patch that entry when they change.

Solid effects do not run during SSR. Pass initial values or getters to 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
}

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.

Did this page help you?