---
title: "ssr:streamChunk Hook"
description: "Hook for inspecting the tags that resolve after the SSR shell has streamed."
canonical_url: "https://unhead.unjs.io/docs/head/api/hooks/ssr-stream-chunk"
last_updated: "2026-09-05T06:20:43.576Z"
---

The `ssr:streamChunk` hook runs inside `renderSSRHeadSuspenseChunk()`, once per chunk. It receives normalized copies of the tags from entries that were still pending after the shell went out. If no entries are pending, the chunk renderer returns early and the hook does not fire.

## Hook Signature

```ts
export interface Hook {
  'ssr:streamChunk': (ctx: { tags: HeadTag[] }) => SyncHookResult
}

type SyncHookResult = void
```

### Parameters

<table>
<thead>
  <tr>
    <th>
      Name
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        ctx.tags
      </code>
    </td>
    
    <td>
      <code>
        HeadTag[]
      </code>
    </td>
    
    <td>
      Normalized copies of the tags from entries pending after the shell
    </td>
  </tr>
</tbody>
</table>

## When It Fires

Inside `renderSSRHeadSuspenseChunk()` the hook runs after the pending entries are normalized. It runs before the renderer splits out streamed body tags, serializes the client patch, and clears the entries.

The client patch is built from the raw entry input, not from `ctx.tags`. The tags are copies for inspection. Changing them does not change what the client receives.

Only core normalization runs on these copies. Shapes that plugins own, such as `_flatMeta` or the legacy `body` prop, pass through as they are. If your listener needs them resolved, resolve them yourself.

Unlike the other SSR hooks, this one is typed `void`. The renderer does not wait for a returned promise before it serializes the patch and clears entries. Keep the listener synchronous.

Normalization only runs when a listener is registered. With no listener, the hook costs nothing.

## Usage Example

```ts
import { defineHeadPlugin } from '@unhead/dynamic-import/plugins'

export const pendingTagsPlugin = defineHeadPlugin({
  key: 'pending-tags',
  hooks: {
    'ssr:streamChunk': ({ tags }) => {
      for (const tag of tags)
        console.log(`Tag arrived after the shell: ${tag.tag}`)
    }
  }
})
```

## Crawler Visibility

Every tag this hook reports was missing from the initial shell. If a tag ends up in the client patch, the browser applies it with JavaScript, so crawlers that skip JavaScript never see it. Tags that qualify as streamed body tags are written into the HTML body instead and stay visible. See [Streaming](/docs/typescript/head/guides/core-concepts/streaming) for which tags qualify.

[`ValidatePlugin`](/docs/typescript/head/guides/tooling/validate-plugin) uses this hook to report `streamed-tag-hidden-from-bots` during development.

For the initial render, use the [`ssr:rendered`](/docs/head/api/hooks/ssr-rendered) hook instead. It receives the serialized payload of the shell, not per-boundary patches.
