back
198 comments
> The quick rule: if you need bidirectional, low-latency communication (chat, collaboration, games), WebSocket; if you only push from the server, SSE is simpler and cheaper to operate.

For most apps just use SSE and the built-in code for making HTTP requests (Fetch) instead of hacking up your own client side JS to make requests over a WebSocket. The latency is the same because modern browsers multiplex HTTP requests over a single TCP connection that is left open.

Maybe if you are making many client requests per second there is an advantage to not sending full headers/cookies/etc... on each request but not if you're sending requests in response to user clicks/touches.

Any sufficiently complicated SPA contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Fetch.

> The latency is the same because modern browsers multiplex HTTP requests over a single TCP connection that is left open.

In my experience this isn’t true; firstly you’re relying on an implementation detail of the platform that you’re executing on, of which you have no control over on the client side. Secondly, even if you aren’t opening a new connection per request, you’re still travelling through an entire HTTP stack implementation rather than the incredibly simple WebSocket protocol - effectively a length and a mask to get the contents, rather than some (in http1 land) fuzzy parser.

If you can guarantee you’re hitting http2 or http3 then you might be closer in latency, but due to the complexity of both I would imagine plain http1 negotiated persistent WebSockets provide the best latency.

> For most apps just use SSE and the built-in code for making HTTP requests (Fetch) instead of hacking up your own client side JS to make requests over a WebSocket.

HTTP doesn't guarantee in-order delivery. Websocket messages do. In-order delivery is important for stateful protocols. For example, you can start a connection by authenticating, then associate the user with the TCP (or websocket) session. Video games often push this way futher. For example, if you log in to a minecraft server, your in-game character is associated with the TCP session.

If you use HTTP fetch requests, you can't guarantee that - for example - the authentication request will reach the server before authenticated messages.

> just use SSE

And then the user opens your website in a handful of tabs, and everything breaks because having enough open SSE connections blocks ordinary http requests to that origin.

You can avoid that by using a shared worker for all your tabs, but then you lose the simplicity advantage.

No mention of WebTransport makes me very doubtful of this article. It's 2026, all browsers support it and it's bidirectional and lower-latency than WebSocket.
I love SSE. But it has real limitations. One not mentioned yet is that you cannot send binary data.
yep 100%. the SSE version is less headache and can easily scale.
There's no advantage of SSE compared to Websocket unless you're using a fan-out proxy.
> Any sufficiently complicated SPA contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Fetch.

Sound advice otherwise, but you could have left this part out of the comment :D

You underestimate how many "sufficiently complicated SPAs" are slapped together low-code projects. Their maintainers have no idea what you're even talking about.

Funny he mentioned Chris McCord as the originator of this technique with Liveview. The reality however predates that with Sync in Rails that you guessed it... was also Chris McCord's doing. Rails at the time didn't have the capacity to handle it so it was just a tech demo then and a big reason why Chris McCord moved to Phoenix. He was once a prolific Rails developer. That guy is helping the web move forward.
We were also doing this at Booking.com many years earlier, using morphdom and custom templating libraries. I think I wrote the first version in 2014-2015.
A good response to this post

https://yagni.club/3mstlyuxe5s26

Close but htmx with SSE and dom swaps and morphing gets you there without reinventing any wheels.

Pretty much every web app I build has this pattern in it from day 1, as they all quickly expand to have a realtime inbox and notifications subsystem to support workflows and agents.

A lot of the people who oppose this technique don't understand context: The right solution to your problem often involves understanding the problem you're trying to solve!

In my case, I work on two Blazor websites: One is standard in-browser WASM with Restful JSON (and some CSV) over http; the other is server-side Blazor that uses the websocket technique that this article describes.

The server-side Blazor approach is for an internal web application that has a lot of quick-and-dirty pages that replace what used to be ad-hoc database queries and ad-hoc scripts. It's not an "industrial strength" web application that requires high scalability, because it's only a handful of employees who use it. It's also a joy to work with. To be specific, we don't need to go through the exercise of designing an API, making sure that contracts serialize, ect, ect, just to slap a UI around what used to be a script.

The WASM page that uses Restful JSON (and csv) is our customer-facing web application: JSON (and CSV) help with debugging; but the cost of making an API is very high. Development on the customer-facing web site moves much more slowly, but it's "worth it" for an industrial-strength site.

Would I build a highly scalable website using HTML over a websocket? Maybe. The issue is time to market: Because you don't have to build an API, you can move faster; but I don't know if scalability issues will arise.

I like the Vue/React/Svelte model of the DOM being a function of the data. For example, in a shopping cart, I add two chocolates, the number against the chocolate, the count at top and a banner encouraging me to reach X total all center around a data structure.

I use Django Ninja, Zod, InertiaJS+Vue and its as easy as using Django's templating engine, but static typing ensures my view doesnt emit unrepresentable data, my TS doesnt accept unrepresentable data, Vue+TS dont allow logic errors in template. AI makes it effortless. Again, the loaded page is a function of the data supplied at the view.

With HTMX, I'm writing several server-side functions to mutate the DOM imperatively and using HTML attributes to call them. Its great for forms but that shopping cart example needs code scattered across multiple functions and templates.

Love how DHTML, ASP.NET Ajax, JSF Ajax kind of keeps being re-invented.
> Place the HTML where it belongs

Well, some drawbacks are not accounted for when replacing HTML parts: input elements lose focus, if some view was scrolled, then it gets unscrolled, jumping under user's pointer etc.

Something else that I think is interesting here is the new HTML streaming APIs in Chrome: https://developer.chrome.com/blog/declarative-partial-update...

These mean that you can for example have a websocket serve just the new HTML, and then let native browser code figure out inserting it into the DOM, without any dependency. I’m guessing things like LiveView could eventually migrate to this if it becomes standard, and eliminate more of their JS bundle.

Just wanted to mention that in PHP besides Laravel Livewire there's also Symfony Live Components[0]

[0] https://symfony.com/bundles/ux-live-component/current/index....

> that loose back-and-forth over HTTP weighs more than an always-open WebSocket

This is definitely true for HTTP/0 and /1. Is it still true under HTTP/3, or has the underlying ‘single conduit, many channels’ model improved things if used correctly?

> Simple, elegant and fast.

Until someone bombs your websocket server and you then have nothing at all.

>Safer against injection: since the server renders and escapes the HTML before sending it over the channel, an attempt to sneak in a <script> travels as inert text and reaches your neighbor's screen as plain letters, not as code. The same architecture that makes a chat trivial makes it immune to XSS.

I strongly disagree with this point, and in general I've seen the reverse is true. Only the client truly knows how it will interpret especially esoteric kinds of html tags and relying on the server for sanitisation is relying on the system furthest from the authoritative renderer.

Are we coming full circle? I feel like I was doing this with Ajax 15 years ago ...
Reading this is especially interesting to me because it's what I've been working towards for the last 14 years or so. Though like this group, it also came together for me piece by piece.

I got interested in this specific idea back in the early days of Firebase I saw someone built a realtime HTML component with PolymerJS called 'collection' and I became consumed by the idea of fully generic realtime self-updating components. My approach is a bit different than OP or that of HTMX though; it's JSON over the wire, not HTML.

I've built a full implementation in Node.js with a set of declarative frontend components.

https://github.com/Saasufy/saasufy-components?tab=readme-ov-...

And https://saasufy.com/

I'm thinking to make open source.

It's nice to see major frameworks coming to a similar conclusion.

I’m not understanding how websockets fixes the issue of pushing JSON to the client side for it to be rendered into HTML. JSON is just a data object can translate into text to place in the HTML document.

Moving this to the backend with a templating engine still requires a formatted object, correct? I like SSR rendering, but I think the diagram with websockets is missing a key step, where the database results still need transformed into a consumable format that gets plugged into the templating engine. That step can’t get eliminated with either model.

Regardless of where the document is built, it needs to consume some format of structured data. Am I missing something?

> the server sends the HTML already built and the client just places it where it belongs

Mind blown.

A much bigger pet peeve of mine is making a SPA when a bunch of HTML pages would do, and would give you sensible URLs and the ability to open more than one tab in the first place.

When you actually need a SPA, I'd only go websockets if I really need that low latency and your clients are close enough in the first place, if they're half the way around the world on a slow connection you have different design constraints. A chat application works just fine over SSE or similar. Client-initiated requests even work with plain old fetch().

I hate modern web development.

I was a Drupal developer back in the day, and I loved its templating system (the fact that HTML was assembled on the back end) and it was so powerful! It allowed for so much customization without leaking the internals of your representation to the front end, which was much more secure, IMO. Now, you have to give your templating logic to the client and potentially expose parts of your system to the end user.

Then, along comes MVC, and everyone drank the Kool-Aid, despite the fact that nobody ever actually implemented MVC purely, because it wasn't designed for the web. It wasn't designed for general systems, either. You may say, "you're wrong! You can adapt any system to MVC!", and I would respond that that is not what I mean. I mean that it is designed for custom applications (what you are referring to), and not frameworks which are for general use. You might claim that it is a framework, and I would point out that the nuance is in where the customization can be controlled and distributed. (Side note: I know MVC has been around a long time, but so have I and I remember when ajax was the hot new toy. MVC took a long time to gain traction and to infiltrate everything... and now we have ultra slow websites with tens of megabytes to download before they can even show a blank page. I stand by my statements and distain for MVC.)

I was building my own CMS that went back to server-side rendering, but I stopped because I just didn't have time to work on it. This may inspire me to do it again, only this time I'll use an LLM to get me through it faster.

"Was Top 7 on Hacker News"

Contrast with something like "had 7th most comments on Hacker News"

Consider (a) algorithmic ranking, (b) votes and (c) discussion, i.e., comments, aka replies

Perhaps in some cases (b) might drive (a) which then drives (c), and of course (a) can drive (b)

As such, (b) votes and (a) ranking are almost always aligned

However, (b) votes and (c) comments are not always aligned

For example, comments with large point accumulation, i.e., votes, and hence high ranking, usually receive some negative replies (source: personal experience)

This can also be true for submissions gaining points rapidly

Allowing unmoderated real-time chat with all visitors is certainly a decision.
I was wondering: Suppose it would be possible to do this with just a normal HTTP requests and create a real-time SPA by keeping an connection open and doing POST requests from I-Frames. I'm absolutely positive that is possible. No javascript, websockets, or SSE, only HTML and CSS and backend logic.

Would this be interesting or inspiring to anyone, apart from users who'd rather not allow javascript in their browser?

It would be nice if it was easy to "place the HTML where it belongs", but in a complex app it's never just-replace: you have to preserve a lot of state, wether it is filters, forms, user selection, scroll position, ... so updating live becomes quite a huge work.

I've seen good attempts with libraries like idiomorph, but still quite some plumbing to do depending on the app.

Instead of fighting about which of the three options highlighted in TFA: HTMX (or Unicorn etc.), SSE or Websockets is "the best" I think we should take time to be extremely thankful that...

At long last we've got very serious contenders actually catching on that have "barely any JavaScript" (TFA's words).

We switched to SSE and couldn't be any happier.

Any tech that allows to reduce the need to reach for JavaScript on the frontend is a godsend.

> Less traffic and less latency per action: a single persistent connection avoids repeating the TCP handshake and the HTTP headers on every interaction.

You don’t need a TCP connection for everything.

If you’re optimizing for that you can consider client-side caching which you can instruct using cache headers that every browser support. That usually reduces heavy hitters by a lot, even if you set the browser TTL to 1 minute which is fine for most of the scenarios.

I used all of them in various projects: "traditional" Ajax-based SPAs, HTML over WebSockets/SSE, etc. Then I found Inertia.js and never looked back.

With Inertia.js, you get the real feel of an SPA without the complexity of maintaining APIs just for the frontend. You can even make some pages plain HTML (like the homepage, legal pages, etc.), while making pages that require reactivity SPAs.

I know the point is to minimize JS here, but have we ever considered sending raw JS over the socket? JavaScript can be a lot more compact than the final DOM that it affects. Dynamic JavaScript is much more interesting than dynamic html. SSR HTML is a boring, solved problem. I need something more exciting in my life these days.
HTTP over WebSockets: Sounds like the same tag line I have been using in my project for the past 2 years.

https://github.com/prettydiff/aphorio

My approach is pretty simple. Since the connection phase of WebSockets is RFC2616 compatible, per RFC6455, you can use the same server logic to connect both.

This gives me too much <script runat="server"> or even JSF vibes.
Having a hard time following the debate—all I want to know, is there a particular use case which makes websockets seem so attractive? A particular network topology?
So basically, this entire conversation and article is summed up by 6 in one hand and a half dozen in the other in regards to this method vs traditional SPA's.
Perfect. If you live in a city, this is deployed to the edge, you have a low latency, fast, unmetered connection and run a M series mac or decent PC.
A few years back I wrote a backend implementation of the LiveView protocol in Typescript (https://liveviewjs.com) and played around with another BunJS-specific implementation (https://hotdogjs.com/). (Also did Java and Go versions but that's another story.)

There isn't an official "protocol" so I had to figure it out by watching the WS traffic and determining how it worked which was fun if not tedious. That said, the more I learned, the more I was impressed by the efficiency and the programming model which felt simpler yet more powerful than SPAs.

I did get to a point where I just got too busy to keep up and over the last couple of years things have changed a bit on the "protocol" side.

But recently (a week ago-ish), I started poking at the old LiveViewJS repo with the help of coding agents. Now that Phoenix is past 1.0 and the JS runtimes (Node, Deno, Bun) have more overlap in terms of APIs and library support, I think it will be more straight forward and frankly easier to get and stay at parity.

> What are its advantages? > There is only one rendering engine, cutting down complexity.

Actually there are 2 rendering, the server sending HTML and the browser drawing it on your screen.

So why not push the logic and send a jpg image of the rendered content itself. Lol

The whole concept of decoupling the frontend and the backend is that they can be agnostic of each other, they need to align but it is not the same skills/concerns.

Serve rendered html like in 2000 and you have recreated a smart way to do what industry spent decades running away from.

Is this satire? Rage bait? Why would you ever do this?

Just make a normal website!! You've invented an MPA with extra steps!

What’s wrong with this idea, really? It’s redundant because you can serve HTML to requests with Apache or Ngnix or any other server on the happy path.

What’s right with the idea, really? It’s exactly what the tried and true preferences of developers have been shown to be: getting in the way of the happy path for no reason.

Now you can have build steps and put story points in Jira and do it all on the server where we don’t have to see it, and the success condition is that the text gets served. Both sides can be happy now.

On a side note, this is possibly the prettiest dev landing page I have ever seen.
Topcoat (Rust) is aiming taking this approach as well: https://github.com/tokio-rs/topcoat. The project is still in the early days. It won't require WebSockets, but WebSockets will be an option.
All of this to finally come back to old web with html generated on server.

It make me happy.