---
title: "Streaming SSR"
description: "Stream head tags as async components resolve during Svelte SSR"
canonical_url: "https://unhead.unjs.io/docs/svelte/head/guides/core-concepts/streaming"
last_updated: "2026-06-30T06:59:43.213Z"
---

Standard SSR waits for everything to render before sending HTML. Streaming sends the document shell immediately, then streams content as async components resolve.

The problem: async components using `useHead()` set head tags *after* the initial render. Without streaming support, those tags never reach the client's `<head>`.

Unhead's streaming integration solves this by injecting `<script>` patches into the stream as content resolves, updating the `<head>` in real-time.

## How It Works

1. **Shell renders** - Initial `<head>` tags render with the document shell
2. **Async content resolves** - Components call `useHead()`
3. **Patches stream** - Unhead injects DOM updates as inline scripts
4. **Client hydrates** - The client head instance picks up the final state

## Setup

### Vite Plugin

The plugin transforms your components to enable streaming head updates:

```ts
// vite.config.ts
import { Unhead } from '@unhead/svelte/vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    svelte(),
    Unhead({ streaming: true }),
  ],
})
```

For webpack projects, import `Unhead` from `@unhead/svelte/bundler` and call `Unhead({ streaming: true }).webpack()`.

### Server Entry

```ts
// entry-server.ts
import { render as _render } from 'svelte/server'
import { createStreamableHead, UnheadContextKey } from '@unhead/svelte/stream/server'
import App from './App.svelte'

export async function render(url: string, template: string) {
  const { head, wrapStream } = createStreamableHead()
  const context = new Map()
  context.set(UnheadContextKey, head)

  const rendered = await _render(App, {
    props: { url },
    context,
  })

  const svelteStream = new ReadableStream({
    start(controller) {
      controller.enqueue(new TextEncoder().encode(rendered.body))
      controller.close()
    },
  })

  return wrapStream(svelteStream, template)
}
```

### Client Entry

```ts
// entry-client.ts
import { mount } from 'svelte'
import { createStreamableHead, UnheadContextKey } from '@unhead/svelte/stream/client'
import App from './App.svelte'

const head = createStreamableHead()
const context = new Map()
context.set(UnheadContextKey, head)

mount(App, {
  target: document.getElementById('app')!,
  context,
})
```

## Usage

Use `useHead()` normally in your components. Tags stream automatically as async content resolves:

```svelte
<script lang="ts">
import { useHead } from '@unhead/svelte'

const { data } = $props()

useHead({
  title: data.title,
  meta: [
    { name: 'description', content: data.description }
  ]
})
</script>

<div>{data.content}</div>
```

## When to Skip

If your Svelte SSR is fully synchronous, stick with standard SSR. The streaming setup adds complexity for no benefit when all head tags are available at initial render.
