Installing Unhead with Svelte
Install @unhead/svelte, create a head with createHead(), and provide it through setContext(). Components can patch useHead() entries from $effect() for reactive updates.
Use Unhead when you need typed SEO input, structured data integration, or script loading beyond Svelte's built-in <svelte:head>. The examples follow Vite's SSR structure; a Vite SPA uses the same client setup.
Demos
Setup
1. Install Unhead
Install the @unhead/svelte dependency in your project.
pnpm add @unhead/svelte2. Set Up Client-Side Rendering
Create the browser instance from @unhead/svelte/client. In a Vite SSR app this usually belongs in entry-client.ts; in an SPA, put it in the main entry.
import { createHead, UnheadContextKey } from '@unhead/svelte/client'
import { hydrate } from 'svelte'
import App from './App.svelte'
import './app.css'
// SSR or SPA usage
const unhead = createHead()
const context = new Map()
context.set(UnheadContextKey, unhead)
hydrate(App, {
target: document.getElementById('app')!,
context
})
import { createHead, UnheadContextKey } from '@unhead/svelte/client'
import { mount } from 'svelte'
import App from './App.svelte'
import './app.css'
// SPA usage
const unhead = createHead()
const context = new Map()
context.set(UnheadContextKey, unhead)
const app = mount(App, {
target: document.getElementById('app')!,
context,
})
export default app
3. Set Up Server-Side Rendering
For SSR, create a fresh instance from @unhead/svelte/server for each request and return it with Svelte's render result.
import { createHead, UnheadContextKey } from '@unhead/svelte/server'
import { render as _render } from 'svelte/server'
import App from './App.svelte'
export function render(_url: string) {
const unhead = createHead()
const context = new Map()
context.set(UnheadContextKey, unhead)
const rendered = _render(App, { context })
return {
...rendered,
unhead,
}
}
After Svelte renders, pass the instance and complete HTML template to transformHtmlTemplate():
import { transformHtmlTemplate } from '@unhead/svelte/server'
// ...
// Serve HTML
app.use('*all', async (req, res) => {
try {
// ...
const rendered = await render(url)
const html = transformHtmlTemplate(
rendered.unhead,
template
.replace(`<!--app-head-->`, rendered.head ?? '')
.replace(`<!--app-html-->`, rendered.body ?? '')
)
res.status(200).set({ 'Content-Type': 'text/html' }).send(html)
}
catch (e) {
// ...
}
})
// ..
4. 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/svelte/server'
export function render(_url: string) {
const head = createHead({
// change default initial lang
init: [
{
htmlAttrs: { lang: 'en' },
title: 'Default title',
titleTemplate: '%s - My Site',
},
]
})
const html = `<!-- your html -->`
return { html, head }
}
Use useHead() for the full head object or useSeoMeta() for flat SEO metadata.
<script lang="ts">
import { useHead, useSeoMeta } from '@unhead/svelte'
// a.
useHead({
title: 'Example Site',
meta: [
{ name: 'description', content: 'An example page description.' }
]
})
// b.
useSeoMeta({
title: 'Example Site',
description: 'An example page description.'
})
</script>
For handling reactive input, check out the Reactivity guide.
5. Enable Auto-Imports (Optional)
unplugin-auto-import can provide the composables without explicit imports:
import { autoImports } from '@unhead/svelte'
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
imports: [
autoImports,
],
}),
// ...
]
})
Next Steps
See reactive input before passing Svelte state to these composables:
Optional integrations:
- Add
useSchemaOrg()for structured data - Use
useScript()for loading triggers and script lifecycle hooks