HTMY Streaming
This example demonstrates how to use the StreamingRenderer from htmy to stream HTML content to the client.
HTML streaming
HTML streaming is a powerful technique that allows you to send parts of the HTML response to the client as they become available, rather than waiting for the entire response to be ready. This can significantly improve the perceived performance of your application by reducing the time to first byte (TTFB) and first contentful paint (FCP), and enabling progressive loading of content.
The application
The application itself will be very simple: a single page that displays a list of items. To demonstrate async HTML streaming, we will create a list item component that simulates a slow operation that takes 1 second to resolve. This way list items will be streamed to the client and appear one by one as they become ready.
First we create an htmy_streaming_app.py file, import everything we need, create the FastAPI instance, and an HTMY instance with the StreamingRenderer:
import asyncio
from fastapi import FastAPI
from htmy import Component, ComponentType, Context, StreamingRenderer, component, html
from fasthx.htmy import HTMY
# Create the app instance.
app = FastAPI()
# Create the HTMY instance with a streaming renderer.
htmy = HTMY(renderer=StreamingRenderer())
Next, we create an async list item component that takes 1 second to resolve:
@component
async def slow_list_item(value: ComponentType, _: Context) -> ComponentType:
"""Async list item component that takes 1 second to resolve."""
await asyncio.sleep(1)
return html.li(value)
In this example we don't create a dedicated page component. Instead, the FastAPI route simply returns the htmy components for the @htmy.page() decorator to render. This means we now have everything in place, except the FastAPI route itself:
@app.get("/")
@htmy.page() # type: ignore[arg-type]
def index() -> Component:
"""The index page route."""
return (
html.DOCTYPE.html,
html.html(
html.head(
html.title("HTMY Streaming Example"),
html.Meta.charset(),
html.Meta.viewport(),
# Use PicoCSS for styling
html.Link.css("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"),
),
html.body(
html.h1("This entire page is being streamed."),
html.p(
"The list items below are asynchronously generated "
"and sent to the client as they are ready."
),
html.ol(
# Render a number of async list items.
*(slow_list_item(f"Item {i}") for i in range(1, 33)),
),
class_="container",
),
),
)
html.ol(*(slow_list_item(f"Item {i}") for i in range(1, 33))) creates an ordered list of 32 slow_list_item components. They will be sent to the client as they get resolved by the streaming renderer.
Run your application
You can now run your application using uvicorn or fastapi-cli:
Or with the FastAPI CLI if installed:
Once the server is running, open your browser and navigate to http://127.0.0.1:8000. You should see the page loading incrementally, with the list items appearing one by one as they get ready.