Vue Academy · Lesson

Streaming SSR with renderToWebStream

renderToWebStream() vs renderToString(), HTTP streaming, TTFB improvement, chunk flushing.

Lesson 3 of 413 steps

Streaming SSR with renderToWebStream is a free Vue Academy lesson on CoddyKit. This is lesson 3 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Vue Academy learning path, and your progress syncs across the web and the CoddyKit app. The Vue Academy course includes 4 lessons in total.

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 chunks

Wrapping 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 markup

Streaming 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:

  • renderToWebStream returns a ReadableStream; Node uses pipeToNodeWritable
  • 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
Free to start

Learn HTML with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
42
Lessons
156

Frequently Asked Questions

Is the “Streaming SSR with renderToWebStream” lesson free?

Yes — any 3 lessons of the Vue Academy learning path, including “Streaming SSR with renderToWebStream”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Vue Academy course includes 4 lessons in total.

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, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 4.

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

  1. Vue Suspense Component
  2. Async Components with defineAsyncComponent
  3. Streaming SSR with renderToWebStream
  4. Deferred Hydration Strategies
← Back to Vue Academy