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.
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.
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.
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.
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.
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.
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 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.
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.
[0] https://symfony.com/bundles/ux-live-component/current/index....
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?
Until someone bombs your websocket server and you then have nothing at all.
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.
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-...
I'm thinking to make open source.
It's nice to see major frameworks coming to a similar conclusion.
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?
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 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.
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
Would this be interesting or inspiring to anyone, apart from users who'd rather not allow javascript in their browser?
I've seen good attempts with libraries like idiomorph, but still quite some plumbing to do depending on the app.
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.
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.
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.
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.
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.
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.
Just make a normal website!! You've invented an MPA with extra steps!
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.
It make me happy.