Streaming HTML with preact
I've been tinkering with server-side rendering lately, and especially HTML’s ability to be streamed to the browser. I find the streaming capability adds another interesting dimension to the spectrum of pre-rendering with hydration, dynamic SSR, and client side rendering.
Historically all websites were either static html pages or rendered by a server language, and sent to the browser. As JavaScript grew more powerful and we got features as Ajax, more sites moved rendering parts of pages to the client. With JavaScript frameworks we saw the conception of Single Page Applications, or SPAs. SPAs move the rendering of the UI from the server and instead require an API from which the application can read and modify the necessary data to render the application. The rise of SPAs matches historically well with the rise of RESTful server APIs. With the rise of the new Javascript meta frameworks, such as Next.js we are coming back to server side rendered and streaming pages.
Streaming
The gist of HTML streaming is that the server can continue sending content as long as the http request is open. By sending down the start of the html directly and not blocking on database or external APIs, can greatly improve the responsiveness of a site.
As a fan of the preact library, which is a minimalistic DOM oriented React API clone, I wanted to explore streaming a server side rendered preact tree.
the idea of out of order streaming means that HTML is being streamed to the client, having pieces of the UI that is depending on lazy data being deferred, and having them streamed down as placeholders, and then when the content is being loaded it can be added to the HTML stream as a little script tag at which quickly replaces itself with the placeholder when it finishes loading. This is something I've been doing in a library called preact-render-to-stream and it uses the built-in renderToStringAsync and also the Suspense functionality that the Preact Compact library offers. The library include a component called Defer which is given a promise, a fallback, an on-error callback and a render callback. If the promise itself is being resolved within 10 milliseconds then it will be inlined directly but otherwise it will no longer wait for that promise, it will render the fallback, continue rendering the stream and as soon as possible get visible content sent to the user, and then append the finished rendered UI to the bottom of the HTML stream.
preact-render-to-stream
Render JSX and Preact components to an HTML stream with out of order deferred elements.
Demo: https://preact-render-to-stream.olofbjerke.com
Note: This is still an experimental package and the API is not stable yet.
If you use preact for dynamic server side rendering (SSR), then this package can help with First Contentful Paint, as it can defer parts of the UI to be streamed in later in the HTTP response.
The package uses the preact-render-to-string package to render the HTML and a special context to defer parts of the UI until their lazy data is ready to be rendered.
This package is inspired by the Flecks library for Ruby.
The package is written in TypeScript and is published as a ES module with type definitions.
Installation
npm install preact-render-to-streamUsage
import http from "node:http";
import { toStream, DefaultHead, Defer } from "preact-render-to-stream";
const server = http.createServer(async (req, res) => {
res.writeHead(200, { "Content-Type": "text/html" });
const stream = toStream(
{ head: <DefaultHead /> },
<>
<header>
<h1>preact-render-to-stream demo</h1>
</header>
<main>
<Defer
promise={new Promise((res) => setTimeout(() => res("Got data"), 1500))}
fallback={() => <p>Loading</p>}
render={(d) => <p>{d}</p>}
/>
<p>This is shown even though the previous component is slow.</p>
</main>
</>
);
for await (const part of stream) {
res.write(part);
}
res.end();
});
server.listen(8000);
Resources: