# Compose & kotlinx.html

DevFeed: [Compose & kotlinx.html](<https://devfeed.tech/articles/compose-kotlinx-html-20925.md>)

Original publisher: [Read original article](<https://jakewharton.com/compose-and-kotlinx-html/>)

Published: 2026-08-26T00:00:00Z

Content type: article

Language: en

Sources: [Jake Wharton](<https://devfeed.tech/sources/jake-wharton.md>)

Topics: [Compose](<https://devfeed.tech/topics/compose.md>), [HTML](<https://devfeed.tech/topics/html.md>), [WebSocket](<https://devfeed.tech/topics/websocket.md>), [Document Object Model (DOM)](<https://devfeed.tech/topics/dom.md>), [ui](<https://devfeed.tech/topics/ui.md>), [client](<https://devfeed.tech/topics/client.md>)

Tags: [backend](<https://devfeed.tech/tags/backend.md>), [compose](<https://devfeed.tech/tags/compose.md>), [html](<https://devfeed.tech/tags/html.md>), [sse](<https://devfeed.tech/tags/sse.md>), [ui](<https://devfeed.tech/tags/ui.md>), [web](<https://devfeed.tech/tags/web.md>), [websocket](<https://devfeed.tech/tags/websocket.md>)

## AI overview

The article presents an approach for keeping server-rendered HTML admin pages fresh without abandoning static HTML. It uses Molecule on the JVM to observe state, kotlinx.html to render HTML fragments, Ktor to stream updates over WebSockets, and client-side JavaScript with Idiomorph to patch the DOM. It also discusses using server-sent events for unidirectional updates and the scalability tradeoffs of long-lived connections.

## Source excerpt

Let's render a simple page with Ktor server and kotlinx.html: get("/users.html") { call.respondHtml { myLayout(title = "Users") { userList( users = db.users.value, ) } } } A reusable myLayout provides scaffolding, userList encapsulates the specific page content, and db.users is a StateFlow<List<User>> from the persistence layer. I've been doing this over and over to create admin dashboard pages for a project. It works great right up until you leave it open for a minute or two, and its content becomes stale. Client-side frameworks exist within the ecosystem to "solve" this, such as Compose for HTML or Compose UI for Web. If your house has a leaky pipe you can also "solve" that by moving to a new house. I simply will not bring myself to abandoning HTML let alone delivery of static HTML in the response. Efforts are underway to adapt Compose for HTML for so-called isomorphic rendering. This would involve performing the initial composition on the server to produce the static HTML for the HTTP response. Then, client side as JS, mounting the same rendering code to reproduce the DOM tree and incrementally update it for future state changes. Tomorrow's Compose today Instead of waiting, I brought my own Compose on the JVM from home in the form of Molecule. Instead of managing a UI tree over time, Molecule manages a single piece of state over time. We can use that to render an HTML fragment on the server as a string, and then stream that to the client. webSocket("/users.ws") { launchMolecule(Immediate) { val users by db.users.collectAsState() createHTML().userList( users = users, ) }.collect(::send) } That's it. Ktor gives us the webSocket, Molecule runs the StateFlow<String> which is piped into it, and kotlinx.html renders the HTML fragment of our existing content function. In the initial HTML payload you need to wire this up somehow. Something like: script { unsafe { +""" |const content = document.getElementById("content"); |const socket = new WebSocket("ws://" + location.ho