HTML Streaming support is a beta feature; breaking changes may be introduced in minor version updates.
// renderer/_deault.page.server.js
export { render }
import { escapeInject } from 'vite-plugin-ssr'
import { renderToStream } from 'some-ui-framework' // React, Vue, ...
async function render(pageContext) {
const { Page } = pageContext
const stream = renderToStream(Page)
return escapeInject`<!DOCTYPE html>
<html>
<body>
<div id="page-view">${stream}</div>
</body>
</html>`
}
// server.js
app.get("*", async (req, res, next) => {
const pageContextInit = { url: req.url }
const pageContext = await renderPage(pageContextInit)
const { httpResponse } = pageContext
if (!httpResponse) return next()
// If `renderToStream()` returns a Node.js Stream:
const stream = await httpResponse.getNodeStream()
// If `renderToStream()` returns a Web Stream:
const stream = await httpResponse.getWebStream()
stream.pipe(res)
})
Examples:
renderToNodeStream()
: /examples/react-full/renderToNodeStream()
: /examples/vue-full/pipeToWebWritable()
: /examples/cloudflare-workers-vue-html-streaming/Stream Docs & API:
We can convert the stream to a string:
// If we provide a stream into our `render()` hook's `escapeInject`, we can
// still get a string.
/* This won't work: (a stream cannot be consumed synchronously)
const { body } = httpResponse
res.send(body)
*/
// But we can do:
const body = await httpResponse.getBody()
assert(typeof body === 'string')
res.send(body)
We can also use Stream pipes.
// renderer/_deault.page.server.js
export { render }
import { escapeInject, pipeNodeStream, pipeWebStream } from 'vite-plugin-ssr'
import { pipeToWritable } from 'some-ui-framework' // React, Vue, ...
async function render(pageContext) {
const { Page } = pageContext
// Node.js Stream
const streamPipe = pipeNodeStream(writable => {
// `writable` is a Node.js writable
pipeToWritable(Page, writable)
})
// Web Stream
const streamPipe = pipeWebStream(writable => {
// `writable` is a Web writable
pipeToWritable(Page, writable)
})
return escapeInject`<!DOCTYPE html>
<html>
<body>
<div id="page-view">${streamPipe}</div>
</body>
</html>`
}
// server.js
app.get("*", async (req, res, next) => {
const pageContextInit = { url: req.url }
const pageContext = await renderPage(pageContextInit)
const { httpResponse } = pageContext
if (!httpResponse) return next()
// Node.js Stream
httpResponse.pipeToNodeWritable(res)
// Web Stream
httpResponse.pipeToWebWritable(res)
})
Some data fetching tools, such as Relay, provide the initial data only after the stream as ended.
In such situations, we can return a pageContext
promise in our render()
hook:
// renderer/_deault.page.server.js
export { render }
export { passToClient }
import { escapeInject } from 'vite-plugin-ssr'
import { renderToStream } from 'some-ui-framework' // React, Vue, ...
const passToClient = ['initialData']
async function render(pageContext) {
const { Page } = pageContext
const stream = renderToStream(Page)
const documentHtml = escapeInject`<!DOCTYPE html>
<html>
<body>
<div id="page-view">${stream}</div>
</body>
</html>`
const pageContextPromise = (async () => {
return {
// Some `initialData` provided after the stream has ended
initialData,
}
})()
return {
documentHtml,
pageContext: pageContextPromise
}
}