Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bcfc456

Browse files
authored
feat: streaming support (#537)
1 parent 269378d commit bcfc456

172 files changed

Lines changed: 12298 additions & 590 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ docs/.nuxt
1717
.angular
1818

1919
.vite-inspect
20+
21+
test-results/
22+
playwright-report/
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
title: Streaming
3+
description: Update head tags dynamically during React streaming server-side rendering
4+
navigation.title: Streaming
5+
new: true
6+
---
7+
8+
::warning
9+
This API is experimental and may change in future versions.
10+
::
11+
12+
Streaming sends HTML to the browser progressively. With React's `renderToPipeableStream()` and Unhead's streaming support, head tags update as suspense boundaries resolve.
13+
14+
## Vite Plugin
15+
16+
```ts
17+
// vite.config.ts
18+
import { unheadReactPlugin } from '@unhead/react/stream/vite'
19+
import react from '@vitejs/plugin-react'
20+
import { defineConfig } from 'vite'
21+
22+
export default defineConfig({
23+
plugins: [
24+
react(),
25+
unheadReactPlugin(),
26+
],
27+
})
28+
```
29+
30+
## Server
31+
32+
```tsx
33+
// entry-server.tsx
34+
import { renderToPipeableStream } from 'react-dom/server'
35+
import { StaticRouter } from 'react-router-dom/server'
36+
import { createStreamableHead, UnheadProvider } from '@unhead/react/stream/server'
37+
import App from './App'
38+
39+
export function render(url: string, template: string) {
40+
const { head, onShellReady, wrap } = createStreamableHead()
41+
42+
const { pipe, abort } = renderToPipeableStream(
43+
<UnheadProvider value={head}>
44+
<StaticRouter location={url}>
45+
<App />
46+
</StaticRouter>
47+
</UnheadProvider>,
48+
{ onShellReady },
49+
)
50+
51+
return {
52+
abort,
53+
pipe: wrap(pipe, template),
54+
}
55+
}
56+
```
57+
58+
## Client
59+
60+
```tsx
61+
// entry-client.tsx
62+
import { hydrateRoot } from 'react-dom/client'
63+
import { BrowserRouter } from 'react-router-dom'
64+
import { createStreamableHead, UnheadProvider } from '@unhead/react/stream/client'
65+
import App from './App'
66+
67+
const head = createStreamableHead()
68+
69+
hydrateRoot(
70+
document.getElementById('app')!,
71+
<UnheadProvider value={head}>
72+
<BrowserRouter>
73+
<App />
74+
</BrowserRouter>
75+
</UnheadProvider>,
76+
)
77+
```
78+
79+
Use `useHead()` normally in components - updates stream automatically as suspense boundaries resolve.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
title: Streaming
3+
description: Update head tags dynamically during SolidJS streaming server-side rendering
4+
navigation.title: Streaming
5+
new: true
6+
---
7+
8+
::warning
9+
This API is experimental and may change in future versions.
10+
::
11+
12+
Streaming sends HTML to the browser progressively. With Solid's `renderToStream()` and Unhead's streaming support, head tags update as suspense boundaries resolve.
13+
14+
## Vite Plugin
15+
16+
```ts
17+
// vite.config.ts
18+
import { unheadSolidPlugin } from '@unhead/solid-js/stream/vite'
19+
import solid from 'vite-plugin-solid'
20+
import { defineConfig } from 'vite'
21+
22+
export default defineConfig({
23+
plugins: [
24+
solid(),
25+
unheadSolidPlugin(),
26+
],
27+
})
28+
```
29+
30+
## Server
31+
32+
```tsx
33+
// entry-server.tsx
34+
import { renderToStream } from 'solid-js/web'
35+
import { createStreamableHead, UnheadContext } from '@unhead/solid-js/stream/server'
36+
import App from './App'
37+
38+
export function render(url: string, template: string) {
39+
const { head, onCompleteShell, wrapStream } = createStreamableHead()
40+
41+
const { readable, writable } = new TransformStream()
42+
43+
renderToStream(() => (
44+
<UnheadContext.Provider value={head}>
45+
<App url={url} />
46+
</UnheadContext.Provider>
47+
), { onCompleteShell }).pipeTo(writable)
48+
49+
return wrapStream(readable, template)
50+
}
51+
```
52+
53+
## Client
54+
55+
```tsx
56+
// entry-client.tsx
57+
import { hydrate } from 'solid-js/web'
58+
import { createStreamableHead, UnheadContext } from '@unhead/solid-js/stream/client'
59+
import App from './App'
60+
61+
const head = createStreamableHead()
62+
63+
hydrate(() => (
64+
<UnheadContext.Provider value={head}>
65+
<App url={window.location.pathname} />
66+
</UnheadContext.Provider>
67+
), document.getElementById('app')!)
68+
```
69+
70+
Use `useHead()` normally in components - updates stream automatically as suspense boundaries resolve.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: Streaming
3+
description: Update head tags dynamically during Svelte streaming server-side rendering
4+
navigation.title: Streaming
5+
new: true
6+
---
7+
8+
::warning
9+
This API is experimental and may change in future versions.
10+
::
11+
12+
Streaming sends HTML to the browser progressively. With Svelte 5's async SSR and Unhead's streaming support, head tags update as components resolve.
13+
14+
## Vite Plugin
15+
16+
```ts
17+
// vite.config.ts
18+
import { unheadSveltePlugin } from '@unhead/svelte/stream/vite'
19+
import { svelte } from '@sveltejs/vite-plugin-svelte'
20+
import { defineConfig } from 'vite'
21+
22+
export default defineConfig({
23+
plugins: [
24+
svelte(),
25+
unheadSveltePlugin(),
26+
],
27+
})
28+
```
29+
30+
## Server
31+
32+
```ts
33+
// entry-server.ts
34+
import { render as _render } from 'svelte/server'
35+
import { createStreamableHead, UnheadContextKey } from '@unhead/svelte/stream/server'
36+
import App from './App.svelte'
37+
38+
export async function render(url: string, template: string) {
39+
const { head, wrapStream } = createStreamableHead()
40+
const context = new Map()
41+
context.set(UnheadContextKey, head)
42+
43+
const rendered = await _render(App, {
44+
props: { url },
45+
context,
46+
})
47+
48+
const svelteStream = new ReadableStream({
49+
start(controller) {
50+
controller.enqueue(new TextEncoder().encode(rendered.body))
51+
controller.close()
52+
},
53+
})
54+
55+
return wrapStream(svelteStream, template)
56+
}
57+
```
58+
59+
## Client
60+
61+
```ts
62+
// entry-client.ts
63+
import { mount } from 'svelte'
64+
import { createStreamableHead, UnheadContextKey } from '@unhead/svelte/stream/client'
65+
import App from './App.svelte'
66+
67+
const head = createStreamableHead()
68+
const context = new Map()
69+
context.set(UnheadContextKey, head)
70+
71+
mount(App, {
72+
target: document.getElementById('app')!,
73+
context,
74+
})
75+
```
76+
77+
Use `useHead()` normally in components - updates stream automatically.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
title: Streaming
3+
description: Update head tags dynamically during streaming server-side rendering
4+
navigation.title: Streaming
5+
new: true
6+
---
7+
8+
::warning
9+
This API is experimental and may change in future versions.
10+
::
11+
12+
Streaming sends HTML progressively. With Unhead's streaming support, head tags update as async content resolves.
13+
14+
## Server
15+
16+
```ts
17+
import { createStreamableHead, wrapStream } from 'unhead/stream/server'
18+
19+
const { head } = createStreamableHead()
20+
21+
head.push({
22+
title: 'My App',
23+
htmlAttrs: { lang: 'en' }
24+
})
25+
26+
const template = '<!DOCTYPE html><html><head></head><body></body></html>'
27+
28+
// Wrap your app stream with head injection
29+
const fullStream = wrapStream(head, appStream, template)
30+
return new Response(fullStream)
31+
```
32+
33+
## Client
34+
35+
```ts
36+
import { createStreamableHead } from 'unhead/stream/client'
37+
38+
const head = createStreamableHead()
39+
```
40+
41+
The client automatically processes queued entries from streaming.
42+
43+
## API
44+
45+
### createStreamableHead
46+
47+
```ts
48+
import { createStreamableHead } from 'unhead/stream/server'
49+
50+
function createStreamableHead(options?: {
51+
streamKey?: string
52+
}): {
53+
head: Unhead
54+
wrapStream: (stream: ReadableStream, template: string) => ReadableStream
55+
}
56+
```
57+
58+
### wrapStream
59+
60+
```ts
61+
import { wrapStream } from 'unhead/stream/server'
62+
63+
function wrapStream(
64+
head: Unhead,
65+
stream: ReadableStream<Uint8Array>,
66+
template: string
67+
): ReadableStream<Uint8Array>
68+
```
69+
70+
Wraps a stream with shell rendering and template injection.
71+
72+
### renderSSRHeadShell / renderSSRHeadSuspenseChunk
73+
74+
For manual control without `wrapStream`:
75+
76+
```ts
77+
import { renderSSRHeadShell, renderSSRHeadSuspenseChunk } from 'unhead/stream/server'
78+
79+
// Render initial shell
80+
res.write(renderSSRHeadShell(head, template))
81+
82+
// After each async chunk, emit head updates
83+
for await (const chunk of appStream) {
84+
res.write(chunk)
85+
const update = renderSSRHeadSuspenseChunk(head)
86+
if (update) res.write(`<script>${update}</script>`)
87+
}
88+
89+
res.write('</body></html>')
90+
```
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
title: Streaming
3+
description: Update head tags dynamically during Vue streaming server-side rendering
4+
navigation.title: Streaming
5+
new: true
6+
---
7+
8+
::warning
9+
This API is experimental and may change in future versions.
10+
::
11+
12+
Streaming sends HTML to the browser progressively. With Vue's `renderToWebStream()` and Unhead's streaming support, head tags update as async components resolve.
13+
14+
## Vite Plugin
15+
16+
```ts
17+
// vite.config.ts
18+
import { unheadVuePlugin } from '@unhead/vue/stream/vite'
19+
import vue from '@vitejs/plugin-vue'
20+
import { defineConfig } from 'vite'
21+
22+
export default defineConfig({
23+
plugins: [
24+
vue(),
25+
unheadVuePlugin(),
26+
],
27+
})
28+
```
29+
30+
## Server
31+
32+
```ts
33+
// entry-server.ts
34+
import { renderToWebStream } from 'vue/server-renderer'
35+
import { createStreamableHead } from '@unhead/vue/stream/server'
36+
import { VueHeadMixin } from '@unhead/vue'
37+
import { createApp } from './main'
38+
39+
export async function render(url: string, template: string) {
40+
const { app, router } = createApp()
41+
const { head, wrapStream } = createStreamableHead()
42+
43+
app.use(head)
44+
app.mixin(VueHeadMixin)
45+
46+
router.push(url)
47+
await router.isReady()
48+
49+
const vueStream = renderToWebStream(app)
50+
return wrapStream(vueStream, template)
51+
}
52+
```
53+
54+
## Client
55+
56+
```ts
57+
// entry-client.ts
58+
import { createStreamableHead } from '@unhead/vue/stream/client'
59+
import { createApp } from './main'
60+
61+
const { app, router } = createApp()
62+
const head = createStreamableHead()
63+
64+
app.use(head)
65+
66+
router.isReady().then(() => {
67+
app.mount('#app')
68+
})
69+
```
70+
71+
Use `useHead()` normally in components - updates stream automatically as suspense boundaries resolve.

0 commit comments

Comments
 (0)