HN Debrief

HTML over WebSockets: real-time SPAs with barely any JavaScript

  • Programming
  • Developer Tools
  • Web Development
  • Open Source

The post pitches a server-driven way to build "single-page" apps without a big frontend framework. Instead of shipping a lot of JavaScript and talking to a JSON API, the server keeps state, renders HTML fragments, and pushes them over a WebSocket so the browser can swap them into the page. The appeal is obvious: one source of truth, less frontend code, and real-time updates without building a separate client app.

If you want server-driven interactivity, start with the boring stack that already fits your framework, especially SSE-based or request/response HTML swaps, before inventing a WebSocket transport. Reserve always-on server state and custom socket plumbing for apps that truly need bidirectional low-latency behavior or for internal tools where delivery speed beats infrastructure neatness.

Discussion mood

Interested but skeptical. People broadly like the return to server-driven HTML for simpler apps, especially because it avoids heavy frontend stacks, but they were unconvinced that WebSockets are the right default and kept pointing to SSE or mature frameworks as the more practical path.

Key insights

  1. 01

    SSE covers most of this pattern

    For the common case of server-to-browser updates, SSE gives you the same HTML-over-the-wire architecture without forcing client actions onto a custom socket protocol. It keeps you inside normal HTTP semantics, gives browsers built-in reconnect support through `Last-Event-ID`, and avoids reimplementing request handling that Fetch already solves well.

    If your UI mostly needs notifications, live lists, or streamed partial updates, try SSE first and keep writes on normal HTTP routes. You will likely ship faster and debug less infrastructure.

      Attribution:
    • hackingonempty #1
    • jallmann #1
    • dzonga #1
  2. 02

    Use the framework that already packages this

    The strongest pro-pattern comments did not defend hand-rolled sockets. They pointed to mature implementations like Rails Hotwire and Turbo, Symfony Live Components, Topcoat, and Inertia.js that already solve partial updates, routing, and state flow in opinionated ways. That shifts the question from protocol purity to how much custom plumbing you are willing to own.

    Do not start from a blog-post architecture diagram. Start from the best-supported server-driven UI stack in your language and only drop lower-level if those constraints actually hurt you.

      Attribution:
    • zemnmez #1
    • conradfr #1
    • carllerche #1
    • felixding #1
  3. 03

    Great fit for internal and CRUD-heavy apps

    This model shines when the product is mostly forms, tables, admin pages, and small real-time touches. In those cases, skipping a separate frontend API and keeping the database as the system of record can dramatically cut code and coordination overhead. The win is speed of delivery, not abstract elegance.

    For internal tools and B2B back offices, optimize for fewer moving parts and shorter feature cycles. Save richer client architecture for the parts of the product that truly need it.

      Attribution:
    • gwbas1c #1
    • x0x0 #1
  4. 04

    Partial DOM replacement still has UI edge cases

    Server-driven swaps are not free just because the payload is HTML. If you replace the wrong DOM nodes, users can lose input focus, scroll position, and pointer context. Libraries like Turbo work because they define precise fragment targeting and swap rules, not because raw HTML magically preserves interaction state.

    Treat DOM patching behavior as a product concern, not an implementation detail. Before adopting this pattern, test editing, scrolling, and concurrent updates on real pages, not just demo counters.

      Attribution:
    • deepsun #1 #2
    • x0x0 #1
  5. 05

    Complex reactive pages still favor data-first UIs

    When multiple UI elements derive from the same shared state, a framework where the view is a function of data stays easier to reason about than imperative fragment updates. Shopping carts, dashboards, and heavily interactive surfaces can become awkward if the update logic is scattered across server actions and template fragments.

    Use server-driven HTML for coarse page and component updates. If many widgets must stay in sync from shared state, a typed client model will age better.

      Attribution:
    • aitchnyu #1
    • wild_egg #1
  6. 06

    This is older than the current branding

    Several people placed the idea in a long lineage that runs from early Ajax and DHTML through JSF, ASP.NET Ajax, Rails experiments, Booking.com internal systems, and then into LiveView and Hotwire. That history matters because it reframes the approach as a recurring equilibrium point for web apps, not a fresh invention.

    Read this category as a mature pattern with known strengths and failure modes. That means you can borrow operational lessons and tool choices from older systems instead of treating it like unexplored territory.

      Attribution:
    • xutopia #1
    • ricardobeat #1
    • felixding #1
    • shaftway #1

Against the grain

  1. 01

    WebTransport may obsolete this debate

    One commenter argued that focusing on SSE versus WebSocket already feels dated because WebTransport promises bidirectional communication with lower latency. Another immediately pushed back that it is still in standards limbo. The useful point is that transport advice ages quickly, while architecture choices last longer.

    Avoid locking your design too tightly to claims about one browser transport being permanently superior. Keep the app-layer contract simple enough that you can swap transports later.

      Attribution:
    • simlevesque #1
    • opendomain #1
  2. 02

    Decoupled APIs still buy real leverage

    The sharper critics were not defending React for its own sake. They were defending separation of concerns, cacheability, and the ability to evolve frontend and backend independently. In that framing, shipping HTML from the server is not simplification. It is coupling that looks cheaper until the product surface grows.

    If your frontend will be long-lived, multi-client, or owned by a different team, the cost of a clean API may be worth paying up front. Do not let short-term speed hide future coordination costs.

      Attribution:
    • mikestorrent #1
    • altairprime #1
  3. 03

    Turbo works because it is limited

    The enthusiastic Rails examples came with an important built-in warning. Turbo feels magical on tables, forms, and list updates because it deliberately does less than React and asks developers to manage the reactivity graph mentally. That simplicity is the advantage, but also the ceiling.

    When evaluating server-driven UI libraries, judge them by their failure boundary as much as their demo. Make sure the parts of your product near that boundary are small enough to isolate or rewrite later.

In plain english

CRUD
Create, read, update, delete, the basic operations behind many business applications that mostly move data in and out of databases.
DHTML
Dynamic HTML, an older term for using HTML, CSS, and JavaScript together to update pages interactively without full reloads.
DOM
Document Object Model, the browser’s in-memory representation of an HTML page that scripts can inspect and change.
Fetch
The standard browser API for making HTTP requests from JavaScript.
Hotwire
A set of tools from the Rails ecosystem, including Turbo, for building interactive apps with mostly server-rendered HTML instead of heavy client-side JavaScript.
htmx
A lightweight JavaScript library that lets HTML attributes trigger HTTP requests and partial page updates without a full SPA framework.
HTTP/2
A version of the Hypertext Transfer Protocol that allows multiple requests and responses to share one connection more efficiently than older HTTP.
HTTP/3
A newer version of the Hypertext Transfer Protocol built on QUIC, designed to reduce latency and connection-level blocking.
Inertia.js
A library that lets server-side frameworks drive single-page app navigation without building a separate public API for the frontend.
JSF
JavaServer Faces, a Java web framework for building server-driven user interfaces with reusable components.
LiveView
A framework pattern, popularized by Phoenix, where the server renders HTML and pushes updates to the browser over a persistent connection.
SSE
Server-Sent Events, a browser feature for receiving a continuous one-way stream of updates from a server over HTTP.
Turbo
The Hotwire library that intercepts links and forms, updates parts of a page, and supports server-pushed HTML changes.
WebSocket
A persistent network connection between browser and server that allows two-way real-time messaging after an initial HTTP upgrade.
WebTransport
A newer browser communication API intended for low-latency bidirectional data exchange, still evolving in standards and tooling.

Reference links

Counterarguments and follow-up posts

Frameworks and libraries

  • Symfony UX Live Components
    Example of an established server-driven UI toolkit in the PHP ecosystem.
  • Topcoat
    Early Rust project pursuing a similar server-driven UI model without requiring WebSockets.
  • Aphorio
    Project described as taking an "HTTP over WebSockets" approach with shared connection logic.
  • LiveViewJS
    TypeScript implementation inspired by Phoenix LiveView, mentioned as evidence that the model is efficient and portable.
  • HotdogJS
    Another Bun-focused implementation in the LiveView family mentioned alongside LiveViewJS.
  • Saasufy Components
    Declarative frontend component system using JSON over the wire instead of HTML.
  • Saasufy
    The hosted product behind the JSON-over-the-wire component approach.

Background references