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?
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.
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
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
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.
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.
Use html5 routing and do “link hijacking”. Ex:
<a href="/about" onclick="your-fn">about</a> where you-fn does
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.
What do you mean by “web sockets story” and how do you think about/implement “offline story”?
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
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.
In js apollo-client https://www.apollographql.com/docs/react/features/server-side-rendering.html
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.
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.
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.
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 am I missing?
This is essentially double the effort, even if it is a better ideal. Is it worth it, practically speaking?
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.
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).