← All posts

HTML over WebSockets: SPAs with barely any JavaScript

The LiveView pattern: server-rendered HTML over a persistent channel—no separate API contract or heavy client framework required.

HTML over WebSockets: SPAs with barely any JavaScript
Contents

In brief

A classic single-page app splits work in two: the client builds UI from JSON, and the backend serves data behind an API contract. Andros.dev revisits another path: HTML over WebSockets. The server sends ready-made markup; a thin client only places it and handles events.

That is the LiveView family—Phoenix LiveView and cousins in Django, Laravel, .NET, and beyond. The point is not to “kill React,” but to choose deliberately where rendering lives and which transport carries updates.

What happened

The article maps the hypermedia family. Ready HTML can travel over plain HTTP (htmx, Unicorn), one-way SSE (Datastar), or a bidirectional WebSocket—the LiveView pattern.

The origin story is ElixirConf 2019: Chris McCord built a real-time Twitter clone with Phoenix LiveView in about fifteen minutes without a client rendering framework. Similar libraries now exist in most languages. Client JavaScript remains, but its job is narrow: open the channel, insert HTML, run animations and gestures—without duplicating business logic in two codebases.

In the usual flow the browser fetches JSON, parses it, and builds the DOM. With LiveView, a persistent channel carries an already assembled fragment: the server queries data, renders a template, and can push updates to connected clients without polling.

Why it matters

For many teams SPA cost is not “pretty components,” but keeping two codebases and contracts in sync. One server template engine shrinks that surface: no API layer only for the UI, session state sits next to the data, and chat or live dashboards come almost for free via broadcast.

There is a price. The server keeps an open channel and often per-client state in memory; horizontal scale needs a shared layer (in Django land: Channels, ASGI, Redis). High network latency hurts the “instant” feel, offline mode is essentially gone, and the learning curve is steeper than dropping htmx on a button.

The author contrasts SSE honestly: if traffic is mostly server to client (feeds, notifications, model token streams), a one-way channel is cheaper to operate. WebSockets pay off when you need dense two-way exchange—collaboration, games, chat.

In practice

  1. Decide whether you need bidirectional real-time, server push only, or plain request/response.
  2. If you pick LiveView, put crawlable content in the first server response—search bots will not see later WebSocket patches.
  3. Design reconnect UX and failure modes; a flaky network will otherwise empty the page.
  4. Budget memory and sticky sessions (or a shared channel layer) early: concurrent connections, not the protocol itself, are the usual bottleneck.
  5. Check ecosystem maturity: Phoenix LiveView 1.x, Laravel Livewire + Reverb, Blazor Interactive Server, Django LiveView / Reactor each trade ops cost for features differently.

The author reports hundreds of concurrent readers on modest hardware without a separate frontend monolith. Enterprise load uses the same ideas with an explicit budget for state and resilience.

Takeaway

HTML over WebSockets is a mature way to ship interactive apps in one language without mandating a heavy client framework. Pick the transport for the job: HTTP for request/response, SSE for cheap push, WebSocket when you need a persistent two-way channel. Architecture beats fashion around any single library.