> 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.
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.
> 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.
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.
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.
> 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.
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.
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.
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.
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.
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.
Is Meteor.js still in use? I recall early on it was all the rage, but that didn't last too long.
I had thought it delivered updated html over sockets, but maybe it was just the data. Did seem to be a bit more of a heavy JS framework, but it's been years.
Frankly I think serving dynamic HTML at all is a problem in an SPA. Use a service worker to cache structure, control, and styling, then use an API to fetch dynamic data in a more efficient manner.
71 comments
[ 1.2 ms ] story [ 10.1 ms ] threadJust make a normal website!! You've invented an MPA with extra steps!
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.
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.
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.
Until someone bombs your websocket server and you then have nothing at all.
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.
https://yagni.club/3mstlyuxe5s26
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.
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.
[0] https://symfony.com/bundles/ux-live-component/current/index....
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.
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.
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.
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.
I had thought it delivered updated html over sockets, but maybe it was just the data. Did seem to be a bit more of a heavy JS framework, but it's been years.
https://forums.meteor.com/t/meteor-3-5-is-out-change-streams...