HTML Over WebSockets in 2026: When to Replace Your SPA

By Diogo Hudson Dias
Engineer in a São Paulo office watching a real-time admin dashboard update on a desktop monitor with servers in the background.

You’re paying the SPA tax for pages that mostly shuffle forms and tables. Meanwhile, your team is fighting hydration bugs and reactivity edge cases that have nothing to do with your business. There’s a simpler path that’s suddenly feeling modern again: HTML over WebSockets — server-rendered UI, diffed and streamed to the browser with barely any JavaScript.

The idea is not new. What’s changed is the production viability. Load balancers, CDNs, and WAFs handle persistent connections cleanly now. Frameworks like Phoenix LiveView, Rails Turbo Streams (Hotwire), Laravel Livewire, htmx, and .NET’s SignalR-backed Blazor Server have matured. A new HN thread this week — “HTML over WebSockets: real-time SPAs with barely any JavaScript” — captures why teams are revisiting it. If you run a B2B product where “real time” means notifications, dashboards, collaborative cursors, and AI token streams, you should evaluate it.

The decision: When HTML-over-the-wire wins

Use this as a go/no-go framework. The more boxes you check, the stronger the case.

1) Your UI is read-heavy, form-heavy, or dashboard-heavy

  • CRUD admin consoles, ops dashboards, back-office tools, B2B SaaS with moderate interaction.
  • Collaborative indicators (presence, cursors), notifications, or sub-10 Hz updates (e.g., table refreshes, status lights) — not 60 fps canvas games.
  • SEO matters? Server-rendered HTML is native SEO. No hydration dance.

2) Your team is backend-heavy or regulated

  • Back-end engineers can ship UI features without a React/GraphQL gauntlet.
  • Security/compliance prefers server as source of truth; less client logic, fewer secrets in the browser.

3) You don’t need robust offline-first

  • HTML-over-the-wire assumes online presence. Offline modes beyond optimistic form retries are painful. If offline is core, stick to SPA/native.

4) You have real-time needs beyond one-way streaming

  • If you only need server-to-client streaming (e.g., AI token output), SSE is simpler. If you also need client pings, live forms, or presence, WebSockets consolidate protocols.

If this sounds like your product, you can likely delete 70–90% of your front-end code on those surfaces. In our delivery pods, CRUD-heavy B2B features moved 25–40% faster after migrating from a React/GraphQL/SPA shell to server-driven HTML with a thin client. Fewer dependencies, fewer state bugs, fewer build artifacts to break.

What “HTML over WebSockets” actually looks like

You keep server-side rendering, but the server sends incremental HTML fragments over a persistent socket whenever state changes. The browser applies diffs to the live DOM. Minimal client code, usually a morphing/patch library:

  • Phoenix LiveView: Server tracks a process per client view; pushes efficient diffs over WebSockets; the client patches the DOM.
  • Rails Hotwire/Turbo Streams: Server emits HTML fragments and directives; the browser morphs DOM by ID or streams inserts.
  • Laravel Livewire: PHP renders components on the server; diffs and events flow over AJAX/WebSockets.
  • htmx: Hypermedia-driven HTML with attributes; add a WebSocket or SSE endpoint; htmx swaps DOM snippets.
  • Blazor Server: Components render on the server; SignalR syncs component diffs to the client.

In practice: you write templates/components on the server; you emit events when the model changes; the framework fans out the smallest possible DOM patches to connected clients. You avoid duplicating validation and business logic on the client. You keep one mental model.

Architecture and scaling without surprises

Connection budgets

Persistent sockets are not free. Plan with conservative, testable budgets:

  • Memory per connection: 2–10 KB app-side plus kernel/TLS overhead. Real numbers depend on framework and per-view state. Phoenix LiveView is famously lean; heavy server-side component state is not.
  • Concurrency per node: 20k–100k WebSocket connections per mid-tier instance (8–16 vCPU, 16–64 GB RAM) is realistic with Elixir/Go/Java. Node/.NET can also perform well with tuning. Don’t guess — load test.
  • Idle timeouts: Set 5–20 minutes on LB and app to avoid ghost sessions. AWS ALB/NLB, GCP HTTPS LB, Cloudflare, and Fastly all support WebSockets; increase defaults (often 60s) to match your UX.

Stateless scale with a channel layer

Don’t pin all app state to a single process. Use a channel/pub-sub layer to broadcast updates and survive failovers:

  • Elixir: Phoenix.PubSub (Redis/NATS optional), Presence for tracking.
  • Rails/Laravel: Redis pub/sub; Fan-out with ActionCable/AnyCable (Rails) or Soketi/Pusher-compatible (Laravel/Node).
  • .NET: Azure SignalR Service or Redis backplane.

With fan-out through Redis/NATS, you can run multiple app replicas behind a normal L7 LB, no sticky sessions required. If your framework stores per-connection server state (e.g., LiveView), use distributed registries and make reconnects cheap.

Backpressure and update coalescing

  • Queue per-connection outbound messages and drop/coalesce if the queue backs up. It’s better to deliver the latest state than every intermediate state.
  • Batch DOM patches at 16–50 ms intervals under burst, to reduce patch storms and layout thrash in the browser.
  • Throttle high-frequency sources (e.g., metrics at 10 Hz); downsample to 1–2 Hz per user-visible widget unless a user explicitly requests higher rates.

Authentication and security

  • Authenticate the WebSocket handshake with a short-lived, signed token derived from the user’s session. Rotate every few minutes. Revalidate on resume.
  • CSRF still matters: validate origin and include CSRF tokens on client-to-server events that mutate state.
  • Input validation: treat socket events like HTTP POSTs. Same schema, same rate limits, same audit trail.
  • WAF: allow the WebSocket upgrade path and set sane frame/message size limits (e.g., 64–256 KB). Log rejects.

Observability you actually need

  • Connected sockets gauge and per-node distribution.
  • Reconnect rate per minute and causes (idle timeout vs. network vs. auth failure).
  • Patch metrics: average and p95 patch size, patches/sec, client RTT p50/p95.
  • Server render time p50/p95 by component/template.
  • Backpressure counters: dropped/coalesced updates.

Alert on reconnect storms, p95 patch size growth, or render-time regressions — these correlate directly with perceived jank.

Protocol choice: WebSockets vs SSE vs “futures”

  • Server-Sent Events (SSE): One-way, text/event-stream over HTTP/2. Great for AI token output or simple notifications. Cheap and dead-simple, but no client events. Browser connection-per-origin limits can bite multi-tab power users.
  • WebSockets: Full-duplex, long-lived TCP tunnel. Best for collaborative inputs, presence, live forms, and unified real-time. Slightly more ops overhead, but still mainstream in CDNs/LBs.
  • WebTransport/QUIC: Promising, but still not ubiquitous in enterprise networks/WAFs. Today, you’ll spend more time fighting middleboxes than shipping features.

Rule of thumb: if you need client-to-server events beyond trivial fetch/POST, choose WebSockets and keep your mental model consistent across features.

Performance: what users actually feel

Most “real-time” enterprise UX isn’t about hitting 16.7 ms frames; it’s about rendering the right thing within ~200 ms and never dropping user edits. In practice:

  • A server-rendered diff under 1 KB + 50–100 ms render + 50–100 ms network RTT = 100–250 ms end-to-end. That feels instant for tables, forms, and counters.
  • The browser does less work: no large client-side VDOM, fewer hydration mismatches, fewer megabytes of JS shipped/cached/invalidated.
  • You pay that work on the server, where code is simpler to test, profile, and secure. CPUs are cheaper than engineers.

We’ve seen admin dashboards drop JS bundle size from 1–3 MB to under 200 KB, eliminate SPA routing entirely, and cut TTI by >50%. Your mileage will vary, but the direction is consistent.

Costs and trade-offs (no rose tint)

What you’ll save

  • Front-end complexity: 70–90% less application JS on qualifying surfaces. Fewer build breaks, smaller dependency tree, smaller attack surface.
  • Feature velocity: In our nearshore pods, CRUD-heavy features moved 25–40% faster post-migration; fewer handoffs, 1 code path for validation and side effects.
  • Debuggability: One stack trace. Server logs correlate directly with UI state.

What you’ll pay

  • Persistent connection footprint: You must capacity-plan WebSockets. Memory bugs now cost you per-connection RAM.
  • Framework coupling: LiveView/Turbo/Livewire/Blazor encode protocol semantics. You’re “opinionated by default.” Migrating later is non-trivial.
  • Offline/latency tolerance: High-latency mobile networks and airplane mode are unfriendly. You’ll need careful retries and state reconciliation — or accept online-only flows.
  • QA shape changes: You’ll write more integration tests that assert DOM patches or end-to-end flows rather than unit tests on a client state machine.

If you run a high-interaction design suite, data grid with complex local editing, or 60 fps canvases, stay with SPA/native where the client must be authoritative. For everything else, server-driven HTML is competitive or superior in 2026.

Picking a stack (opinionated shortlist)

  • Phoenix LiveView (Elixir): If you can run on the BEAM, do it. You get first-class WebSockets, cheap processes, Presence, and a mature diff protocol. Production reports of 100k+ sockets per VM are normal with sane state. Excellent for collaborative apps.
  • Rails + Hotwire (Turbo Streams): The lowest-friction path for Rails shops. Great DX, batteries included. Pair with Redis for broadcast. AnyCable improves performance at scale.
  • Laravel Livewire (+ Alpine): PHP teams move fast here. Use Soketi or a managed Pusher-compatible service for fan-out. Keep component state lean.
  • htmx (+ your server): Language-agnostic, super incremental. Start by swapping a single widget to SSE/WebSockets. Perfect for surgical migrations inside legacy stacks.
  • .NET Blazor Server: Enterprise-friendly and well-supported. Be mindful of per-connection memory; profile aggressively. You get SignalR and strong tooling.

Don’t chase exotic frameworks to save 20 ms. Pick the one your team can debug at 2 a.m.

Migration playbook: build wide, ship narrow

  1. Identify one high-churn surface: an internal dashboard, ops console, or a read-heavy customer page with frequent updates.
  2. Instrument it today: measure p50/p95 TTI, JS bundle size, error rate, and time-to-ship for small UI changes (e.g., add a column, add a filter).
  3. Rebuild just that surface with HTML-over-the-wire (htmx or your framework’s server-driven UI). Keep the rest of your SPA intact.
  4. Run dual for 2–4 weeks under a feature flag. Compare metrics A/B: shipping velocity, error rates, infra cost, and user-perceived latency (real user monitoring).
  5. Scale the pattern to similar surfaces. Avoid trying to “replatform” everything. Dashboards and forms first; keep complex editors in SPA land.

Operational checklist (copy/paste)

  • Load balancer: Upgrade timeouts to 300–1200 s for /ws endpoints. Confirm WebSocket support on CDN/WAF path.
  • Autoscaling: Scale on connected sockets, CPU, and queue depth — not just HTTP RPS.
  • Channel layer: Redis/NATS for fan-out. Monitor pub/sub lag.
  • Security: Short-lived auth tokens, origin checks, CSRF on mutating events, message size limits.
  • Backpressure: Per-connection queues with coalescing and drop policies. Never block the event loop on a slow client.
  • Metrics: socket count, reconnects/min, patch size p95, render p95, RTT p95, coalesced drops.
  • Chaos: Kill a node during a demo. Your reconnect behavior is your UX.

What about AI token streams?

Most teams stream model output to the browser as text tokens. If that’s your only real-time need, SSE remains the simplest path. But many products also want live input (tool calls, function streaming, code suggestions with keystroke context) and shared presence. One WebSocket per session lets you multiplex both directions under a single backpressure policy and a single auth story. You can still tunnel token streams as line-delimited events inside a socket frame and render them into HTML snippets server-side.

Bonus: you keep model prompts, redaction, and formatting server-side. No prompt tokens or sensitive context in the client. That matters for enterprise buyers.

Numbers for the CFO

  • Infra: Expect a modest increase in server CPU/RAM for render + connections. For a typical B2B dashboard app with 5–20k concurrent users, incremental infra costs are often in the low thousands per month — far less than one senior front-end headcount.
  • Velocity: Teams report 25–40% faster delivery on CRUD/features where business logic moves server-side. Our mixed US–Brazil pods have seen two-week cycles drop to 1.2–1.5 weeks for similar scope once the SPA scaffolding is out of the way.
  • Reliability: Fewer client dependencies means fewer emergency patches for package breakage. Your risk shifts to server capacity, which you already know how to manage.

These are planning numbers, not guarantees. Do a two-sprint pilot and measure your own baselines.

Bottom line

HTML over WebSockets is not a nostalgia play. In 2026, it’s a production-grade way to ship real-time apps with fewer moving parts. If your product is forms, tables, and dashboards with moderate interactivity, you don’t need a mega-SPA. Keep the brain on the server. Stream the UI. Measure the results. Expand where it wins.

Key Takeaways

  • Use HTML-over-the-wire when your app is form/table-heavy, SEO-sensitive, and only moderately interactive.
  • Pick WebSockets over SSE when you need bi-directional events; use SSE for output-only streams.
  • Plan capacity: 20k–100k sockets per node is achievable; budget a few KB per connection plus framework state.
  • Adopt incrementally: start with one dashboard or admin page; A/B metrics before expanding.
  • Instrument the right signals: sockets, reconnects, patch size, render p95, RTT p95, backpressure drops.
  • Expect fewer front-end bugs and 25–40% faster delivery on qualifying features; pay more attention to server capacity.

References: htmx and WebSockets, Phoenix LiveView, Turbo Streams, Blazor Server

Ready to scale your engineering team?

Tell us about your project and we'll get back to you within 24 hours.

Start a conversation