Streaming SSR with renderToWebStream
renderToWebStream() vs renderToString(), HTTP streaming, TTFB improvement, chunk flushing.
Streaming SSR with renderToWebStream is a free Vue Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Vue Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
From renderToString to Streaming
Classic SSR uses renderToString, which builds the entire HTML in memory before sending a single byte. The browser waits idle until the whole page is ready.
Streaming SSR sends HTML in chunks as the app renders, so the browser starts parsing earlier.
renderToWebStream Basics
Vue's server renderer offers streaming functions. renderToWebStream returns a ReadableStream (Web Streams API), while renderToNodeStream/pipeToNodeWritable target Node's stream interface.
Each chunk is flushed as the component tree resolves.
import { renderToWebStream } from 'vue/server-renderer'
import { createSSRApp } from 'vue'
import App from './App.vue'
const app = createSSRApp(App)
const stream = renderToWebStream(app)Piping to an HTTP Response
In a Node server you pipe the stream into the response. Using pipeToNodeWritable you write directly to the res writable, flushing chunks as they are produced.
import { pipeToNodeWritable } from 'vue/server-renderer'
server.get('*', (req, res) => {
const app = createSSRApp(App)
res.write('<!DOCTYPE html><html><body><div id="app">')
pipeToNodeWritable(app, {}, res)
})Streaming in a Web/Edge Runtime
In edge runtimes (Workers, Deno, Bun) you return a Response built from the web stream directly. No Node streams involved.
import { renderToWebStream } from 'vue/server-renderer'
export default async function handler(request) {
const app = createSSRApp(App)
const stream = renderToWebStream(app)
return new Response(stream, {
headers: { 'Content-Type': 'text/html' }
})
}How Chunks Reach the Browser
As Vue renders nodes, it pushes serialized HTML strings into the stream. The HTTP layer flushes those bytes immediately. The browser's incremental HTML parser renders visible content before the response finishes.
This is why the user sees the header and layout while the page body is still streaming.
TTFB: The Key Metric
TTFB (Time To First Byte) measures how long until the first response byte arrives.
With renderToString, TTFB includes the full render time. With streaming, the first chunk leaves the server almost immediately, dramatically lowering TTFB and improving perceived speed.
// renderToString: client waits for full HTML
const html = await renderToString(app) // blocks
res.end(fullPage(html))
// streaming: first bytes flush right away
pipeToNodeWritable(app, {}, res) // non-blocking chunksWrapping the Stream with Layout HTML
You usually need to send the document shell (<head>, opening tags) before the app stream and the closing tags after. Write the prefix, pipe the app, then write the suffix when the stream ends.
res.write('<!DOCTYPE html><head><title>App</title></head>')
res.write('<body><div id="app">')
pipeToNodeWritable(app, ctx, {
write: (chunk) => res.write(chunk),
end: () => res.end('</div></body></html>')
})Error Handling Mid-Stream
Once bytes are flushed you cannot change the HTTP status code. Handle render errors via the onError option (or the writable's error path). For critical failures detected early, prefer non-streaming render so you can still send a 500.
pipeToNodeWritable(app, ctx, {
write: (c) => res.write(c),
end: () => res.end(suffix),
destroy: (err) => {
console.error('Stream failed:', err)
res.end() // status already sent
}
})Hydration After Streaming
Streamed HTML is still hydrated on the client with createSSRApp().mount(). Vue attaches listeners to the existing DOM rather than recreating it. The streaming only affects how the markup is delivered, not how hydration works.
// client entry
import { createSSRApp } from 'vue'
import App from './App.vue'
createSSRApp(App).mount('#app') // hydrates streamed markupStreaming and Async Data
For data-dependent components, resolve critical data before/while streaming. Streaming shines when parts of the tree are independent: the shell streams instantly while slower sections render as their data settles.
// fetch critical data, then stream the rest
const app = createSSRApp(App, { initialData })
const stream = renderToWebStream(app)
return new Response(stream, {
headers: { 'Content-Type': 'text/html' }
})When to Choose Streaming
Use streaming for content-heavy pages where TTFB and perceived speed matter, and where you can send a stable shell first. Stick with renderToString for small pages or when you need full control over the final status code and headers.
Quick Check
Test your understanding of streaming SSR.
Recap
You learned streaming SSR:
renderToWebStreamreturns a ReadableStream; Node usespipeToNodeWritable- Chunks flush as the tree renders, so the browser parses incrementally
- This lowers TTFB versus the blocking
renderToString - Write the document shell around the stream; errors after first flush cannot change status
- Hydration with
createSSRApp().mount()is unchanged
Frequently asked questions
Is the “Streaming SSR with renderToWebStream” lesson free?
Yes — the full text of “Streaming SSR with renderToWebStream” is free to read here on the web, and the Vue Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Vue Academy course, upgrade to CoddyKit PRO.
What will I learn in “Streaming SSR with renderToWebStream”?
renderToWebStream() vs renderToString(), HTTP streaming, TTFB improvement, chunk flushing. You practise Vue Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Vue Academy?
No prior experience is required. Vue Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Streaming SSR with renderToWebStream” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Vue Academy lesson?
Yes. Every Vue Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Vue Suspense Component
- Async Components with defineAsyncComponent
- Streaming SSR with renderToWebStream
- Deferred Hydration Strategies