Skip to content
Dev Tools Article

HTML over the wire is back — the WebSocket part is optional

Server-rendered fragments beat shipping a framework, but SSE gets you most of the benefit with less ops pain.

Lenn Voss
Lenn Voss
Cloud & Infrastructure Writer · Aug 12, 2026 · 4 min read
HTML over the wire is back — the WebSocket part is optional

A post making the rounds this week pitches a familiar idea with fresh energy: build your "SPA" by streaming server-rendered HTML fragments over a WebSocket, and ship almost no JavaScript. The browser keeps a persistent connection open, the server pushes <div>s instead of JSON, and a tiny client runtime swaps them into the DOM. No React, no client-side state store, no API layer to design and version.

The pattern works. It's been in production for years under half a dozen names. But if you're evaluating it in 2026, the interesting question isn't whether HTML-over-the-wire is legitimate — it clearly is — it's whether the WebSocket part is actually the right transport. Increasingly, the answer is no.

This isn't new — that's the point

Every major backend ecosystem independently converged on this architecture, which is about the strongest signal you can get that it solves a real problem. Phoenix LiveView shipped it for Elixir back in 2019, riding the BEAM's absurd capacity for cheap persistent connections — the Phoenix team famously demonstrated two million WebSocket connections on a single server years before LiveView existed. Basecamp extracted Hotwire from HEY in late 2020 and made "HTML over the wire" a slogan. Microsoft's Blazor Server does the same dance over SignalR. Laravel got there with Livewire 3 plus Reverb, its first-party WebSocket server. Django has a small cottage industry of implementations, including the post author's own Django LiveView.

And the lineage runs deeper than any of these. Rails was rendering server-side partials and splicing them into pages with RJS in 2006; PJAX and Turbolinks refined the idea a decade ago. The JSON-API-plus-client-framework SPA was the detour. This is the web's default architecture reasserting itself with a push channel bolted on.

What you're actually buying

The concrete win is deleting an entire layer of your stack. No REST or GraphQL API to design, document, and keep backward-compatible with your own frontend. No client-side data store mirroring your database. One rendering path, on the server, in the language your team already writes, with your existing auth and your template engine's XSS escaping. For a small team building a dashboard, admin panel, or internal tool with live-updating data, that's weeks of work that simply doesn't exist.

The client payload collapses too. htmx is around 14 kB gzipped; LiveView's and Turbo's runtimes are similarly small. Compare that with a typical React + state management + data-fetching bundle before you've written a feature. And latency per interaction genuinely improves: one persistent connection, no per-request TLS and connection setup, HTML that's ready to paint the moment it arrives.

The HN discussion around the post — 90-plus comments — largely conceded all of this. The pushback landed somewhere else.

The socket is the weak link

What you give up is the stateless request/response model that fifty years of web infrastructure is optimized for. Every connected user now holds open state on a specific server. That means sticky sessions or a shared pub/sub backplane (in practice, Redis) the moment you run a second instance. It means load balancers and corporate proxies that idle-timeout or outright block WebSocket upgrades. It means autoscaling gets ugly, because draining a server now disconnects thousands of users mid-session, and reconnection logic has to rebuild whatever state the server was holding. Blazor Server's circuit-lost error screen is the canonical failure mode; every framework in this family has its own version.

Here's the thing: most apps in this pattern's sweet spot don't need bidirectional streaming. They need the server to push updates and the client to send occasional actions. Plain HTTP handles the actions; Server-Sent Events handle the push — over ordinary HTTP, with automatic reconnection and Last-Event-ID resume built into the browser. That's why the most interesting recent entrant, Datastar, is SSE-first rather than WebSocket-first, and why Turbo Streams was designed to work over SSE and plain form responses, not just Action Cable. Even the original post's lone code sample is htmx's SSE extension, not a socket.

The author reports his own site handling peaks of 600 concurrent readers on minimal hardware — plausible, though it's a single-source anecdote about a mostly-read-only site, which is exactly the workload where SSE would do the same job with less machinery.

Where this should land in your stack

If your team is backend-heavy and you're building something server-authoritative — dashboards, admin tools, CRUD apps with live data, notifications, presence — this pattern is production-ready and has been for years. Pick the implementation native to your stack: LiveView on Elixir (still the best-engineered version, because the runtime was built for exactly this), Hotwire on Rails, Livewire on Laravel, htmx or Datastar anywhere else. Start with SSE and plain HTTP; reach for WebSockets only when you actually need low-latency client-to-server streaming, like cursors or collaborative editing.

If you need offline support, optimistic UI, or serious client-side interactivity — canvas tools, editors, anything where the interesting state lives in the browser — this pattern fights you the whole way, and a round trip per keystroke will never feel native. Physics doesn't negotiate: a user 150 ms from your server feels every one of those milliseconds on every interaction.

The honest framing is that this architecture didn't eliminate complexity; it moved it from the client bundle to server memory and your ops runbook. For a lot of teams that's a fantastic trade — servers are easier to observe and reason about than a thousand browser environments. Just be clear that you're making a trade, not getting something for free. The "barely any JavaScript" pitch is real. The "barely any operational complexity" pitch, when WebSockets are involved, is not.

Sources & further reading

  1. HTML over WebSockets: real-time SPAs with barely any JavaScript — en.andros.dev
  2. HTML over WebSockets: real-time SPAs with barely any JavaScript (discussion) — news.ycombinator.com
  3. HTML Over WebSockets — testdriven.io
Lenn Voss
Written by
Lenn Voss · Cloud & Infrastructure Writer

Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.

Discussion 0

Join the discussion

Sign in or create an account to comment and vote.

No comments yet

Be the first to weigh in.

Related Reading