---
title: "Installing Unhead with Svelte"
description: "Set up Unhead in Svelte with createHead() and useHead(). Advanced features beyond svelte:head for SEO, structured data, and scripts."
canonical_url: "https://unhead.unjs.io/docs/svelte/head/guides/get-started/installation"
last_updated: "2026-07-21T05:10:03.514Z"
---

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](https://vite.dev/guide/ssr.html); a Vite SPA uses the same client setup.

<note>

Using [SvelteKit](https://svelte.dev/docs/kit)? See the dedicated [SvelteKit integration guide](/docs/svelte/head/guides/get-started/sveltekit) instead.

</note>

## Demos

- [StackBlitz - Unhead + Vite + Svelte SSR](https://stackblitz.com/edit/github-ckbygkxk)
- [StackBlitz - Unhead + Vite + Svelte SPA](https://stackblitz.com/edit/vitejs-vite-tfv9egtq)

## Setup

### 1. Install Unhead

Install the `@unhead/svelte` dependency in your project.

<module-install name="@unhead/svelte@next">



</module-install>

### 2. 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.

<code-block>

```ts [src/entry-client.ts]
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
})
```

```ts [src/main.ts]
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
```

</code-block>

### 3. Set Up Server-Side Rendering

<note>

Serving your app as an SPA? You can [skip](#4-add-head-tags) this step.

</note>

For SSR, create a fresh instance from `@unhead/svelte/server` for each request and return it with Svelte's render result.

```tsx [src/entry-server.ts]
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()`:

```ts [server.ts]
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.

```ts [src/entry-server.ts]
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.

```sveltehtml [App.svelte]
<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](/docs/svelte/head/guides/core-concepts/reactivity) guide.

### 5. Enable Auto-Imports (Optional)

[unplugin-auto-import](https://github.com/unplugin/unplugin-auto-import) can provide the composables without explicit imports:

```ts [vite.config.ts]
import { autoImports } from '@unhead/svelte'
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    AutoImport({
      imports: [
        autoImports,
      ],
    }),
    // ...
  ]
})
```

## Next Steps

See [reactive input](/docs/svelte/head/guides/core-concepts/reactivity) before passing Svelte state to these composables:

- [`useHead()`](/docs/head/api/composables/use-head)
- [`useSeoMeta()`](/docs/head/api/composables/use-seo-meta)

Optional integrations:

- Add [`useSchemaOrg()`](/docs/schema-org/api/composables/use-schema-org) for structured data
- Use [`useScript()`](/docs/head/api/composables/use-script) for loading triggers and script lifecycle hooks
