At some point I hope it becomes obvious that well-engineered SSR webapps on a modern internet connection are indistinguishable from a purely client side experience. We used this exact same technology over dialup modems and it worked well enough to get us to this point.
Being able to click a button and experience 0ms navigation is not something any customer has ever brought to my attention. It also doesn't help much in the more meaningful domains of business since you can't cheat god (information theory). If the data is so frequently out of sync that every interaction results in JSON payloads being exchanged, then why not just render the whole thing on the server in one go? This is where I can easily throw the latency arguments back in the complexity merchant's face - you've simply swept the synchronization problem under a rug to be dealt with later.
Yes, a well-engineered SSR webapp could be indistinguishable from an SPA. However, it is much harder to build a well engineered SSR with the tools we have. I haven't seen someone solve errors with form submissions and the back button well at the framework level. Post-Redirect-Get was awful. Trying to solve back buttons and wizards. Trying to solve modals. Is a modal a separate page with the rest in the back? What does closing a modal mean? What does a sidebar mean? How about closing it? Pretty soon, you're in half-an-SPA already.
And since you don't want a 2000 character URL, you're either storing half of the session on the server or having to build an abstraction with local storage. And since our frameworks didn't evolve to handle that, what is the purpose?
The key insight into the SPA is that you are writing a coherent client experience. No SSR framework figured out how to do this because they thought about pages rather than experiences.
Let me be clear: I am speaking about web applications. If you're providing information and only have a small number of customer interactions, an SSR is superior. CNN should not be an SPA.
All of the SSR webapps I've built had these solved at a framework level. Dot net and PHP.
Like, the back button: there is no logic because this isn't react. It's just the browser back button. You don't have to do anything if you're using SSR. Back button problems only apply to SPAs or hybrids.
The only real use case for an SPA is something that has to continue to work offline. There are legitimate cases like this, but most apps developed as SPAs aren't it.
> No SSR framework figured out how to do this because they thought about pages rather than experiences.
Laravel, Blazor and apps designed around HTMX are all like this. "SSR framework" has literally nothing to do with "pages rather than experiences". Pages are just a medium to deliver experiences.
The original idea behind an SPA was to enable API-only backends (with static file service). I still think that's a very worthy use case.
Why not decouple the backend from the concerns of particular views? It makes for a more complex frontend, but it also allows multiple, highly differentiated frontend apps to be build on top of a single set of backend APIs. Esri's ArcGIS Online powers a lot of specialized frontend-only web apps. I used to be on their Business Analyst team (that's a web app) - and I have to say I really like the pattern of building apps this way.
That's not a use case, that's an implementation. What is the end user actually getting with this architecture that can't be obtained by the server-side one? Offline functionality is the only differentiating feature.
A use case for an architecture pattern is a way it could be used to implement something. The concept of a "use case" doesn't just apply to end-users of a software product.
> What is the end user actually getting
More things implemented in the products with less development time.
I'm not sure what you're trying to argue. SSR sites and apps still have scripting and client-side interactivity, the only difference is where the rendering is happening. That's what SSR stands for. SSR switches from APIs exchanging JSON with heavy client-side rendering, to REST endpoints returning hypertext which eliminates the majority of client-side rendering.
How would you implement Google Maps as a SSR app? -- same question for ArcGIS, Figma, an arcade game, or a Zoom clone. It's a serious question -- do you think that would be possible for any of those?
I contend that to build a coherent, usable web app of any of those types, there has to be a lot of client-side scripting. Hence, a SPA is the best architectural pattern.
Sure, you could use SSR for the layout of the page... but in that case, why not skip HTML rendering altogether by shipping raw HTML/CSS for the app's layout? Then you'd only need JS to power the contents of <div>s where the app's functionality is located.
> How would you implement Google Maps as a SSR app? -- same question for ArcGIS, Figma, an arcade game, or a Zoom clone. It's a serious question -- do you think that would be possible for any of those?
> I contend that to build a coherent, usable web app of any of those types, there has to be a lot of client-side scripting. Hence, a SPA is the best architectural pattern.
"Lot's of scripting" does not automatically entail "SPA".
That's a very impressive demo of Datastar, and I like his minimalist approach to frontend architecture.
However, the display logic is implemented as a web component containing (a) a lot of tags written as a big HTML file for streaming state from the backend, and (b) a canvas element rendered by a WASM component. If that fits your definition of SSR for frontend apps, no problem and I think it's great. But that's not what I would have defined as SSR.
Some hurdles I imagine Datastar will need to overcome before this pattern can see widespread adoption:
- there doesn't seem to be a straightforward way to statically bind the frontend markup to the backend, so scaling this to multi-team apps will be a challenge.
- the markup and associated binding feels very similar to template-driven SPA frameworks (data- attributes for everything, really? That's fine in the resulting DOM tree but it's got to be a pain to author and maintain for large apps). Perhaps if someone could build a JSX-like code-to-markup DSL, that problem could be ameliorated.
- he is demoing it from his local machine, so we don't get to see (a) how much server-side compute is required at any scale (even >10 users), (b) how strongly latency can affect rendering, and (c) overall bandwidth requirements.
That being said, I know video game companies have streaming games deployed, so in theory this approach has merit (though I suspect only users who lack beefy home computers and have great internet connections opt for it).
Overall, this feels like less of an indictment of the SPA pattern in general* and more of a "let's see what happens if we change what we assume the constraints are."
*Yes, he's right that a lot of modern SPA development tooling is terrible. Dojo from the 2000s is still arguably easier to develop in than most modern SPA frontend stacks, especially since it can be run with only a static file server, no build required.
I'm not indicting anything, I'm simply pointing out that there are a lot of assumptions that SPAs are the only or best way to have a great user experience, that server interactions are slow, that DOM updates are slow, and that API-based SPAs are the only way to solve all of these issues. The Datastar demo disproves all of these, regardless of how you feel about Datastar's specific approach to it.
Which leads me back to my original point that the only feature an SSR can't intrinsically provide is offline functionality.
> Yes, a well-engineered SSR webapp could be indistinguishable from an SPA. However, it is much harder to build a well engineered SSR with the tools we have.
Clearly you've never used Laravel + Livewire. Modals, forms, wizards, sidebars, I have all of that in my app without writing any client-side JavaScript. And it works better than most SPAs. I actually get gushing praise for how "smooth" the app experience is.
My contention is that this may not be the traditional client side app, but you are still placing these on a single page. Just because you are replacing the HTML on the page doesn't mean it is a multi-page app. It's an interesting SPA/MPA hybrid but just because you are not writing javascript doesn't mean that the infrastructure isn't using javascript to handle the plumbing.
So, let's use this as an example. Let's say you bring out a side drawer to edit the details of one row on the table. The side drawer pops up. The user edits details and clicks submit. (To answer this question, the user scrolls to other parts of the table to look at other rows.) There is an error in the user's input based on business logic. The user corrects it, and the row is changed. The side drawer goes away.
How many times is the whole page loaded from scratch? In a traditional SPA, the page is loaded once. With a strict MPA, the page is loaded from scratch four times. With Laravel + Livewire, to my understanding, the page is loaded once and divs are replaced with HTML from the server.
Even if it is not a react app, it is still a collection of single page apps with server side intermediations using html.
> The key insight into the SPA is that you are writing a coherent client experience.
This is the best way to put it I've yet seen. HN articles keep saying things like "now that navigation transitions are solved in CSS, there's no use case left for SPAs". Is everyone just writing apps for widespread content consumption or something?
> CNN should not be an SPA.
Yes, and we need canonical "that should be an SPA"-type apps to bring up in these discussions--which can be hard, since all the best SPAs are for getting work done, not publishing content for the public to consume. Thus, as a class they tend to be department-procured B2B apps and not as generally recognizable. I propose GMail and Google Docs/Sheets/Slides for starters.
> At some point I hope it becomes obvious that well-engineered SSR webapps on a modern internet connection are indistinguishable from a purely client side experience.
I dunno; other than the fact that there are some webapps that really are better done mostly client-side with routine JSON hydration (webmail, for example, or maps), my recent experimentation with making the backend serve only static files (html, css, etc) and dynamic data (JSON) turned out a lot better than a backend that generates HTML pages using templates.
Especially when I want to add MCP capabilities to a system, it becomes almost trivial in my framework, because the backend endpoints that serve dynamic data serve all the dynamic data as JSON. The backend really is nicer to work with than one that generates HTML.
I'm sure in a lot of cases, it's the f/end frameworks that leave a bad taste in your mouth, and truth be told, I don't really have an answer for that other than looking into creating a framework for front-end to replace the spaghetti-pattern that most front-ends have.
I'm not even sure if it is possible to have non-spaghetti logic in the front-end anymore - surely a framework that did that would have been adopted en-masse by now?
> Being able to click a button and experience 0ms navigation is not something any customer has ever brought to my attention
With modern CSS transitions, you can mostly fake this anyway. It's not like javascript apps actually achieve 0ms in practice - their main advantage is that they don't (always) cause layout/content flashes as things update
I read HN all the time on my phone, and I love that it loads reliably even on 1 bar of 4G. Meanwhile, Reddit no longer works reliably even with 3 bars of 5G.
The former is HTML with a light sprinkling of JavaScript, the latter is a SPA app.
Being able to click a button and experience 0ms navigation is not something any customer has ever brought to my attention. It also doesn't help much in the more meaningful domains of business since you can't cheat god (information theory). If the data is so frequently out of sync that every interaction results in JSON payloads being exchanged, then why not just render the whole thing on the server in one go? This is where I can easily throw the latency arguments back in the complexity merchant's face - you've simply swept the synchronization problem under a rug to be dealt with later.