When is it best NOT to do a [reagent] SPA?

Do you agree with Why I Hate your SPA? What are your rules of thumb for deciding if it’s the right technology for the job?

3 Likes

How does URL centric approach plays with ClojureScript frameworks like Om Next and Fulcro which use Graph Database instead of REST APIs? I’m beginner to Fulcro.

Properly using routing (if you have access to the back-end handlers as well), routes work the same way with SPA as with SSR. That is, paste an address (which need NOT have a # in it) and it will just work, because the JS router will read the URL given and do the right thing. The server just happens to be serving the same landingpage for all routes, and client-side handles the rest.

1 Like

I’ve never been much of a fan of SPA and largely agree with the article. The only time SPA makes sense is when you need a very rich interaction model like google maps, or you need offline support. For the vast majority of apps we are just accepting+displaying data while connected and SPA is a poor choice/compromise for many reasons.

Im super excited to see what comes of Phoenix Liveview, it seems a far more promising path for the future: https://youtu.be/Z2DU0qLfPIY?t=664

2 Likes

I agree with the problem of SPAs that break the web (urls, history etc.). And for simple static content indeed it makes no sense. A well done SPA is just fine though.

– edit: missing word

2 Likes

I’m currently rewriting apps that were build using the approach mentioned in the article into SPA’s both at work and at home.

He makes an interesting point, while I agree with some of the things that he points out I think it’s not the complete story.

Downsides to that approach:

  • Maintaining template/logic on the server & client has it’s challenges. A lot of the time they get out of sync.
  • What’s the offline story ?
  • What about websockets story ?
  • Do you have an api that you’re offering to external services, does you website use that api ?
  • Can you easily reuse data that you just fetched ?
  • What about all those page refreshes when loading more items in listing or changing the page ?

The author makes a point that the experience might be better for non-spas. But he’s referring to “half-baked” implementations. I think the SPA user experience is a lot better if you get it right.

The reason I think most SPA’s don’t get it right (also guilty of this on some older projects) is because it’s a lot harder. And there are a few things that people don’t pay attention when there writing them.
A few opinionated thoughts on this:

Use a framework
SPA’s mean that you have a lot of things going on in the client side. You can go about using a minimalist library and doing your own thing where you know how everything works, not that much of a learning curve etc… Personally I really like the idea of standing on the shoulder of giants , building you’re own is just building you’re own “framework”, most of the time this means undocumented, improvised/not well thought out, unless you work on it nothing improves. Doubt a lot of people can do anything remotely close to fulcro, re-frame, keechma, citrus. There documented, well tested, have a lot of libraries like debugging tools etc… just don’t see any plus in the long run for doing you’re own thing instead of using one of those and contributing to them.

Normalize you’re app-state
In SPA’s you have a lot of state, no more refresh and start off with a clean slate. Fulcro has state normalization built in at it’s core, in re-frame it also seems to be the recommended approach.

Use a graph-api
Since most of you’re logic is on the front-end, the backend api should allow the frontend to easily request exactly the nested data structures it needs (restfull api just adds a lot of complexity & network overhead). You can use data-oriented api like om-next query syntax or GraphQl. I am leaning twards graphql since it’s an industry standard and something that I can also expose to third-party clients, the om-next query is pretty clojure specific. The best part is that pathom also has support for graphql so you can have a graphql server and in the client pathom will handle the om-next query sintax <-> graphql conversions.

Now you have a nice graph-api backend, but how does you’re client take full advantage of it ? You can have smart loaders and keep the components in sync with them, but this can be streamlined. Here using something like fulcro in clojurescript or apollo-client & relay in javascript starts to make a lot more sense. What these have in common is that the’re designed to work with a graph-api & have mechanisms built in for normalizing the data app-state. The big idea there is component “co-located queries” & auto-normalization. Now if you add/remove something from the component query the changes will be reflected in the backend api call.

Implement Server side rendering
Rum and fulcro do ssr on the JVM pretty well. With a bit of work seems like reagent could also work nicely.
If you can, nodejs works nicely with clojurescript, I really like the nodejs & npm experience with shadow-cljs a few other examples apps here.

Html links
Use html5 routing and do “link hijacking”. Ex: <a href="/about" onclick="your-fn">about</a> where you-fn does e.preventDefault() and triggers the route change. This way if you disable javascript and have server side rendering it should work as a normal website.

Pre-load route content
I see this a lot… you change to a different screen and see a bunch a loading states. Why not just change the route, add a loading marker and stay on the same page until the content for the page loads. Then just trigger the screen change. It’s not really that hard if you start with this idea from the beginning a nice read js and redux. The user experience is a lot nicer and it also simplifies the UI.

Start with code-splitting
A big of a headache but you need to keep the bundle size as small as possible. I’ve found it really hard to add code-splitting mid-way into a project. Think it’s really worth it to build your app with code-splitting in mind.

10 Likes

What do you mean by “web sockets story” and how do you think about/implement “offline story”?

If you go with backend renders html & frontend just has some javascript that enhance the experience (popups, load more, etc…), this usually results in mini-js apps that each have there “own state” and the end result is a mix between what the server sent and what js added/changed.

Adding something like “if the item stock is 0 mark it as sold in all the listings in real-time” becomes a lot more tricky. There’s also that really really small edge-case where an action is dispatched you’re page is loaded but the js has not finished loading/connecting, for mobile first 3G speed do not help.

Just think what you can do with sockets is a lot more tricky and limited with that approach.

For SPA’s the story can be a lot more simpler. You have a state, the end result is based entirely on that state, you don’t have refreshes when changing pages. The client app gets a notification and can look in the state and choose if it wants to re-render or not. You can take it further and do all sorts of funky stuff like not go to the server and just show from the state when revisiting etc…

For offline story I really like approaches like. You’re in a listing visit 3 items then network goes down (lets say 3G and you’re in a tunnel, or the pages il loaded but no 3g now), you have the option to implement something like gray out all the items except those 3 and let the user revisit them, showing the data that was loaded in the state.
A really cool presentation about PWA’s, link starts off at demo for the offline stuff, but it’s worth watching the entire presentation https://youtu.be/m2tvYGCdOzs?t=1041

1 Like

What I’m getting from these good responses is that now-adays, SSR really never beats a properly-designed SPA if the page is, indeed, an application (as opposed to a blog or simple file server). Correct?

Basically, if you get the “html5 routing” right (browser back button, page refresh, send a link etc…), yep spa should beat a old fashioned webpage on a lot of fronts, after it’s loaded.

The after it’s loaded part is pretty important. A SPA ideally should also have SSR. You can do:

a) The server just renders a loading screen (± something static), and after the app loads it shows the content. Ex: load on mobile https://www.reddit.com/
This is not quite ideal because 1. it makes life harder for search-engines 2. it’s annoying to have the load screen and then see the content after 1-3 sec (with a normal web-page you would see the content faster)

b) Server generates the app-state, renders the complete html + the app-state in a script so that the client on first load can get it from the server.
This can be a bit harder to pull of depending on what you libraries you use. But the end result is that you will only know that it’s a SPA after you click something and see how fast it loads with no refresh.

Fulcro Fulcro Developers Guide

Re-frame re-frame/docs/External-Resources.md at master · day8/re-frame · GitHub

In js apollo-client Server-side rendering - Apollo GraphQL Docs

Think the SPA landscape will get better in time as more and more people start figuring out what works & what does not work and start sharing there setups as boilerplates with minimal app setup.

In clojure I haven’t seen to many fully-featured SPA templates, but hopefully more will appear. In js it’s pretty cool that you have stuff like.

https://github.com/kriasoft/react-starter-kit
https://github.com/react-boilerplate/react-boilerplate

2 Likes

I agree with this. SPAs are usually not worth the trade off in development speed.

Err… who are you agreeing with?

Oh, sorry, the author in the linked article. I don’t HATE single page apps, but I don’t think it’s a great idea to start a new project (with no users, no revenue, no anything) as a single page app.

Of course, if you’re updating a site that is used or freelancing and someone heard react is the new Boyd’s toast, then by all means, SPA away

A lot of the time I see SPA frameworks used for sites which just don’t need them. Why re-implement browser functionality (HTML history, routing) in JS? What if I don’t have JS enabled?

In my experience when SPA’s have been adopted it’s because the backend HTML generation is such an unoptimised mess that it seems like adopting an SPA is the only way to get some semblance of performance.

A well optimised non-SPA can load pages very quickly; the main ‘downside’ from user POV is the page refresh. Does anyone know of any studies that show users care about that?

Also a question to people in the know, the example app that gets thrown around is Google Maps, is it actually possible to build apps of that scale with SPAs to the required performance? I have seen OpenLayers/Leaflet type libs wrapped by SPA frameworks but not implemented with one.

Edit: This started as a reply to @swlkr, but eventually took a turn in a more general direction.

I seem to see a lot of very vague arguments on both sides of the SPA vs. non-SPA debate. Do any real studies exist? What I am looking for in particular are more detailed comparisons of the effort required to build an application using either approach.

Many proponents of the non-SPA side mention that creating an SPA is harder than a non-SPA, but only mention two negatives: the fact that history and scrolling have to be implemented in Javascript; and the fact that potential users could be in a context where Javascript is not available. I don’t see either of them as truly convincing. In the first case, because browsers implement APIs for history and scrolling, which SPA frameworks can use to do the heavy lifting so I don’t have to; in which case the argument is that it is harder falls apart. In the second case, because if I am debating whether or not to build my product as an SPA, I am almost certain that my potential users will be using a Javascript-enabled browser; otherwise I would not even be considering it.

I do see a potential argument for building my product as a non-SPA: Letting one server do the rendering of markup for thousands or even millions of clients will, mainly because of caching, be less taxing on the environment because fewer total CPU cycles are spent rendering the same pages. However, I am not certain that is a valid argument, because of the dynamic nature of most applications, where most pages change content all the time as users interact with the application; thus defeating the cache.

On the other hand there is the argument from many SPA proponents that you cannot build fast-loading and interactive sites without an SPA. The flashing of pages is mentioned most often, but also that any interactivity through Javascript will eventually be slower because it has to construct state anew on each new page.

These arguments seem more sensible on the surface, but I am not entirely convinced. Something like turbolinks exists to remove the flashes as I understand it, and I am not sure the overhead of reinitiating websockets connections and other state on each new page is really that much of a problem.

What I am getting from reading various blogs on this debate in is that it is going to be difficult building a highly optimised web application, regardless of whether or not it is an SPA. Frameworks exist to help you with both approaches, but more exist for non-SPA than SPA simply because that approach is older. For me, that means the choice is really one of philosophy, not of technology: Do I believe that Javascript is a problem and should be used as little as possible; or do I believe that Javascript is the evolution of the web.

What am I missing?

4 Likes

Chiming in. Nobody has to sacrifice server-side rendering when doing an SPA. A good solution IMHO is making isomorphic apps, so that search engines and clients without JavaScript can still use the website.

For a fresh project, I would say do a server rendered website first, then make it isomorphic to have simpler UI code and homogeneous data model, then SPA. We’re following this road and we’re at the “make it a SPA step”. There’s some complexity for us developers involved but the benefits will be faster navigation since there’ll be no need to redownload, reparse HTML/CSS/JavaScript. And we’ve already optimized most of our server side rendering time (most pages below 50-60ms).

2 Likes

This is essentially double the effort, even if it is a better ideal. Is it worth it, practically speaking?

1 Like

Here’s the guideline I’m using:

If you are building an application and need to use an HTML document browser with a crummy document object model, build an SPA. Your users will appreciate the almost-as-good-as-a-desktop-app speed and the almost-but-not-quite-identical user experience to a desktop app.
If your users are working with that app more than 5 minutes a day, they couldn’t care less about loading times of 1 or 10 seconds.

If you are building a web site, you might as well render it from the server.

1 Like

What effort is doubled? I don’t get it.

Implementing logic/views in the front-end (SPA) and re-implementing on the back-end (SSR).