Streaming HTML

thunderbong1 pts0 comments

Streaming HTML<br>Jun 5, 2026<br>Streaming HTML

Getting a stream of HTML via fetch()

How do we get a stream of text from a fetch call? response.text() decodes the response as text but returns it all at once, not as a stream. For that reason, it is not appropriate for our use case. response.body is a stream, but a stream of Uint8Array, not of text. We therefore need to pass the response.body byte stream through a TextDecoderStream().

let response = await fetch('/partial.html');<br>let decoder = new TextDecoderStream();<br>response.body.pipeThrough(decoder)

The above code can be improved by making use of the new textStream() method. textStream() returns a stream of string chunks. textStream() is equivalent to piping the body through a utf-8 TextDecoderStream(), so the previous code can be rewritten as:

let response = await fetch('/partial.html');<br>response.textStream()

Using the fetch API is not the only way to obtain a stream of text. Blobs also have a .textStream() method. Or see the example of using getWriter() on the Chrome for Developers blog.

Now that we have a stream of HTML, we can stream it into the page.

Streaming HTML into the page

Chrome Canary recently added new methods for streaming HTML into the DOM. The streamHTML method, for example, will stream the HTML into the targeted element, replacing any previously existing contents.

const div = document.querySelector('div');

const response = await fetch('partial.html');<br>response.textStream()<br>.pipeTo(div.streamHTML());

streamHTML sets the content of the element

streamReplaceWithHTML replaces the element with the new HTML

streamAppendHTML adds the HTML as the last child of the element

streamPrependHTML adds the HTML as the first child of the element

streamBeforeHTML adds the HTML before the element

streamAfterHTML adds the HTML after the element

There are also equivalent unsafe versions of these methods:

safe streaming methodsunsafe streaming methodsstreamHTMLstreamHTMLUnsafestreamReplaceWithHTMLstreamReplaceWithHTMLUnsafestreamAppendHTMLstreamAppendHTMLUnsafestreamPrependHTMLstreamPrependHTMLUnsafestreamBeforeHTMLstreamBeforeHTMLUnsafestreamAfterHTMLstreamAfterHTMLUnsafe<br>The methods without Unsafe in their name will always sanitize the HTML. The unsafe methods do not perform any sanitization by default (although they can optionally make use of a sanitizer). Read my previous articles about setHTMLUnsafe and setHTML and the Sanitizer API for more information about the difference between safe and unsafe DOM methods.

Comparison with non-streaming methods

Along with all the new streaming methods, there are also new non-streaming equivalents. Here’s the full list of all the new methods:

safeunsafestream safestream unsafesetHTMLsetHTMLUnsafestreamHTMLstreamHTMLUnsafereplaceWithHTMLreplaceWithHTMLUnsafestreamReplaceWithHTMLstreamReplaceWithHTMLUnsafeappendHTMLappendHTMLUnsafestreamAppendHTMLstreamAppendHTMLUnsafeprependHTMLprependHTMLUnsafestreamPrependHTMLstreamPrependHTMLUnsafebeforeHTMLbeforeHTMLUnsafestreamBeforeHTMLstreamBeforeHTMLUnsafeafterHTMLafterHTMLUnsafestreamAfterHTMLstreamAfterHTMLUnsafe<br>The non-streaming unsafe methods are equivalent to the following legacy methods:

modern methodlegacy equivalentsetHTMLUnsafeinnerHTMLreplaceWithHTMLUnsafeouterHTMLappendHTMLUnsafeinsertAdjacentHTML(beforeend)prependHTMLUnsafeinsertAdjacentHTML(afterbegin)beforeHTMLUnsafeinsertAdjacentHTML(beforebegin)afterHTMLUnsafeinsertAdjacentHTML(afterend)<br>Despite the naming not making it clear, the older methods are always unsafe.

There are two key differences between all the new methods with Unsafe in their name and the older methods:

the new Unsafe methods support declarative shadow DOM (a somewhat niche requirement)

the new Unsafe methods have a runScripts option

Declarative partial updates

The new streaming methods are part of a larger interrelated feature called declarative partial updates.

Rather than needing to querySelector an element in the DOM to stream into, elements can be stream-appended to the body. By default, all safe methods remove elements, so you need to specify a sanitizer to allow them:

const response = await fetch('templates.html');<br>response.textStream()<br>.pipeTo(document.body.streamAppendHTMLUnsafe());

If the fetched HTML contains the following markup:

template for="side-panel"><br>h1>Sidebar contenth1><br>template>

template for="main"><br>h1>Main contenth1><br>template>

The initial HTML of the page can make use of processing instructions using the following syntax:

main><br>?marker name="main"><br>main><br>div><br>?marker name="side-panel"><br>div>

The contents of each template will stream into the position of the marker with a corresponding name. Matching name and for attributes determine which part of the page gets updated by which template.

If you want to use a safe method to stream elements onto the page, be aware that by default, if you don’t specify a sanitizer, safe methods remove elements. Specify a sanitizer to allow elements:

const response =...

html methods stream response streaming unsafe

Related Articles