The Single-Page-App Morality Play
baldurbjarnason.com
baldurbjarnason.com
For example I had to maintain a legacy PHP project, relatively old school PHP before there was even OO, plain HTML with just a bit of JS sprinkled it. Everyone complained about it and sure the code was horrible but I could dive in and be productive on day one.
Same company, more recent project with a more modern stack, trying to follow all the OO patterns and everything. I was not even able to fix minor bugs in any reasonable time even studying the code for weeks. What happened? While the developers made good technical choices and the quality of the code in isolation was fine, it was horrible mismanaged. There was no continuity, just people working on it for a couple of days then doing something totally different and forgetting everything again on repeat. Individual design decisions were "industry best practice" but the end result was something I wished I could just delete.
Everyone loves to play wiht the newest toys but you got to keep you developer vanity in check. Sometimes the boring CRUD app should use boring technology. With horrible management you will learn to hate what you once loved anyway.
But that said there _is_ important progress being made in web tech but very little of it has to do with how you mash your HTML, JavaScript and CSS together.
Take a look at what's being done with tech like WebAssembly and full on 3d grpahics in the browser (if youre using a phone I recommend that you connect to a wifi spot to not use up your data).
https://threejs.org/examples/#webgl_animation_keyframes
Theres a whole other can of worms to deal with like finding talent (this kind of content is way more labour intensive and requires much stronger domain knowledge). But the point is that there is a path for enhancing online UX and disrupting user expectations. The value add for being able to review 3d models of construction or order previews on demand speaks for itself.
Every framework is about making the janky uniform across operating systems and browsers.
Programmers don't want to have to learn how to anti-jank on 40 different browsers. They want to learn how to anti-jank on 1 framework and get on with life.
I'm not sure how or why this came about, but it seems to be a code-word meaning one of the newer javascript frameworks, that single word is almost a shibboleth at this point for an entire culture of development.
Most of the day was me: let's do <this easy, normal thing in the ORM.> Them: well, this is more like a half-baked rails knockoff so things like that aren't there.
So, they knew it was lousy, but picked it because it was JS? Sigh.
There is a web development culture. There was a pace of advancement for many years that helped shape this desire for newest shiniest stuff. Other ingredients such as chasing FAANG, VC money, and the general youth of the average web dev. Salaries chasing talent due to the online boom and corporate policies not adjusting to keep talent, thus needing to do resume building for the next job hop because it's going to happen in less than 2 years.
This is a great description of my current work. Multiple systems, stacks, and technologies to support. It's just constantly jumping from one thing to the other. It gets even worse when you throw PRD support work in there. The context switching is brutal.
Certain voices in the team were terrified of "siloing", and so it was a matter of policy that everyone had to jump tasks and teams every two weeks, unless they had a solid case for a longer term task.
The result was that no one knew what the hell they were doing, because no one had time to learn anything before they were shuffling chairs again.
We were only saved from this madness when a major data center failure took out the app and all process theatre was temporarily suspended during crisis mode. Once we got back online, it was quickly realized that we'd all been wildly more productive and not just because of the crisis, but because we could actually function organically without the artificial strictures.
It's possible it's intentional though. It's hard to leave the team or company when you're just ok at a lot of things, but not an expert in anything.
> Sometimes the boring CRUD app should use boring technology.
Absolutely. I would even say most of the time, CRUD and relational databases can be a much better solution.
> As developers, we need to operate under the assumption that good management is the exception, not the norm. Multi-Page-Apps and hybrid frameworks let under-resourced, mismanaged developer teams deliver reliable and safe code. That should not be dismissed lightly.
There's also a helpful "Summary" section at the bottom. Overall it frames the SPA vs. TWA[0] debate in terms of which approach is likelier to produce successful software... or successfully produce software, at least.
[0] Traditional Web Application? Do we call it that?
Personally, I like TWA - will start using that locally to see how it goes. Look for a doc update near you soon...
> I keep seeing Single-Page-Apps with huge JS files that only, in terms of concrete User Experience (UX) benefits, deliver client-side validation of forms plus analytics. Apps rarely leverage the potential of a Single-Page-App. It’s still just the same ‘click, wait for load’ navigation cycle. Same as the one you get with Multi-Page-Apps. Except buggier and with a much slower initial loading time.
Very true - and also of mobile apps that behave in this way, either because they're using the same SPA technology or are just misconceived.
However:
> The Multi-Page-App forces the team to narrow the scope to a level they can handle. It puts a hard limit on their technological aspirations.
I don't see how this follows at all?
The link to https://doriantaylor.com/agile-as-trauma is fantastic too, as is the whole "I am Vanity" section. But yes, I also agree with the people here asking what this has to do with SPA vs MPA, because I don't understand the scope argument.
(Frankly I could write a paragraph response to each section as if it were a separate blog post, that's how much good content there is here)
SPAs are magical pixie dust that lets you easily code a native experience in the web. Some management thinks this. So do under-experienced dev teams.
MPAs provide natural pushback against unrealistic demands and feature requests that your experience might not let you recognize, or that company politics won't let you do.
“We need a sortable table on this route and it should support pagination without a reload, but the pagination needs to be persisted to the address bar for outside link sharing.” This feature alone can introduce all kinds of unmanaged state and be a testing nightmare. And if another part of the site uses the same functionality, you’ve just signed up for an lifetime of maintenance, usually somewhere between your server-side templates, and your asset pipeline. God help you if your bugs can be described as, “I have some local state here, but I’m not sure how to get it either up or down to the right place.”
Personally, these kind of code smells demoralize me enough to consider a job elsewhere, preferably at a company that uses something like React where there’s bound to be complexity, but at least there’s component boundaries to keep your mental facilities from being overrun by a “simple multi page architecture.”
How would you do that differently in a SPA? Or would that just preclude the link sharing, removing the requirement altogether?
Are SPAs doing client-side pagination or something?
> I’ve written in the past about how conferences are the
business equivalent of morality plays. They don’t exist to
educate or enlighten but to unify a community around shared
values—which is why they are great for networking. That is
why they need a liturgical adversary; somebody who can
represent the opposing heterodoxy.
>> unless it's the accusation of "virtue signalling" being
flung around.
You can't have one without the other.In SPA, a single app consists of all your frontend system. hence has a high scope.
While in MPA mode, js is split across multiple pages, and each page has a narrow scope for the javascript code to function.
Hence MPA will limit power and misuse of javascript
In the 2000 it was clear: if you wanted to find informations, you went on the www. If you wanted to chat, you used an IRC app. If you wanted to type a document, you opened a text processor. If you wanted to see a movie, you used a movie player. If you wanted to listen to music, you opened a music player. If you wanted to game, you fired a game. Now all this are on the web.
It's not clear to me if I am a web developer or an app developer because the borders are erased day by day.
I want to read more like this.
[1] https://doriantaylor.com/agile-as-trauma
[update: holy cow, this one too https://www.baldurbjarnason.com/2021/software-crisis-2/
Today is turning into "read about existential problems with how software is developed" day for me]
I really like the MPA approach. To me it means building a bunch of mini SPAs. You can still stuff everything into a single SPA to deploy to a mobile app, but it gives you the advantage quicker first-time page loads and a lot of extra flexibility.
Compared to the Web 1.0 way of doing things where you POST and then reload the page, you're taking an API based approach so when a 3rd party needs to integrate with you - that part is probably done already.
A lot of sites use the MPA approach wrong though. They load the "apps", then proceed to make a whole bunch of asynch requests for data. In most cases, using a mechanism to inject in data when a page first loads makes everything feel a lot faster.
It's the best of both worlds: you get the speed of the old school web and the flexibility of modern practices.
<-- Fully MPA ... MPA with mini SPAs ... Fully SPA. -->
Many don't venture in the middle. Not sure why. Do not stay on either end. It will bite you!
Further, it's a compromise that is difficult to explain and justify to management. If they are risk averse then they won't want to move far from the safety of the frameworks at either end of the spectrum.
If you are a developer of frameworks, it also seems advantageous to go to one end of the spectrum because it's easier to reason about the problem space. When you target the middle there starts to be more variations in how to handle it best. It's really the only place to go though, if folks really want to innovate and provide better experiences for all.
You can pre-load objects in the page and then use AJAX to finalize loading. The user experience is snappier.
The bundle size also remains smaller. Divide and conquer.
Yes, it does require being comfortable navigating back and front end. Over time, a good developer will get there.
It's funny how the article goes on about "If managers were just good enough". But the problem is managers face real world constraints. They can't just give us unlimited resources to live out our complex ego-driven tech fantasies.
I don't disagree terribly with the trope aspect, but people are hurting from the cost to evolve and maintain projects using approaches that sit at the extremes. There needs to be some understanding, and possible embrace, of elements of the middle ground in order to be truly maintainable in a lot of cases.
My call to action would be more for the innovators and framework developers to think more about this. Companies can try to solve it for themselves, but forces like staff turnover prevent meaningful progress for most.
Most of the time, an SPA can be limited to one action or group of actions. For example one SPA to manage the user's preference, another for managing a list of objects, so on and so forth.
There doesn't seem to be anything inherently more complex about routing on the front end vs the back end. Why does it turn out to be messy when the back end isn't? What's keeping you from using the same patterns?
For example, you can use mustache.js to load a cached "Preferences" template and just grab the data from the server to fill it in, or from the browser's IndexedDB if you've stored that data there.
Does this approach not qualify as a single SPA?
I have JS turned off for accessibility and privacy reasons. What annoys me is sometimes JS is needed just to render a bit of text when it could be rendered with plain HTML. Not everything needs to have JS in order to work/function. Also: many hamburger menus are rendered useless when JS is turned off when it could be implemented with CSS alone.
Hyperlinks are the defining feature of the WWW; that people put resources on the WWW that can't be linked to, is cause for regret.
There are two things you could be referring to here. Both are resolved so that you can have an SPA that basically starts out as an MPA and upgrades itself to an SPA, though it’s common for scripting to still be required where it shouldn’t be.
Firstly, putting the route in the path rather than the fragment. That was resolved with history.pushState which shipped across all browsers in 2010–2012. You can safely assume it these days for all JavaScript-enabled browsers (you should not be supporting such browsers as IE9, Firefox 3.6, Chrome 4, Safari 4 and Opera 10.1).
Secondly (dependent on the first), whether the server’s response includes the content. Some SPAs don’t (reasonable for many applications, generally unreasonable for content websites), some do, typically using a family of techniques broadly called server-side rendering (search the term for explanations). It’s only in the last few years that SSR has seen significant uptake, though it’s existed in varying forms since at least the mid-’00s (though there were some fairly significant problems with it until history.pushState existed, so it wasn’t widely done except for Google’s #! → ?_escaped_state_= AJAX crawling scheme from 2009).
Yuh, I guess my disconnect is that I tend to avoid sites that don't work at all without JS. I'm willing to enable JS if I care about the site (and it at least renders something without JS), but I'm not prepared to drop my pants for some random sales site that gives me a white screen if JS is disabled - someone else gets my business.
Maybe I don't understand what you're asking. Browsers these days have functionality that enables SPAs to navigate between URLs, push/pop to and from the back history, etc. without full page reloads.
Well if that's right, then this site is not a "single-page application". It has multiple pages with different URLs, and I don't know how that's different from any other website with server-side state.
[Edited to mention state; state is irrelevant to whether its an MPA or an SPA, but without state it's hard to argue its an application at all]
So without a page-refresh, does anything get stored in browser history? Does the back-button work?
So even though you're requesting a different address, you're technically not getting an entirely new "resource." No matter where you hit the address, you'll get the same base blob of HTML/css/js, but going to the URL directly will fire the proper navigation (through js) to get you to the spot you requested (which may involve loading different resources, dynamically).
Hopefully that explanation makes sense
and modern frameworks allow you to use normal urls directly, by configuring server to ignore url path, and let SPA handle it (along with HTTP 404)
So, it is not a technical limitation of SPA.
The root cause of https://vector-of-bool.github.io/ scrolling incorrectly when navigating between pages isn't due to bad management or lack of care, it's because it's a single page app that didn't handle scrolling correctly (which a browser would provide for free), and the author didn't notice despite coming in with good intentions.
https://developer.chrome.com/blog/shared-element-transitions...
I don't think the vector-of-bool website, and the broken JS transitions, were designed by a dysfunctional organization (or an organization for that matter) (though the author has suffered from burnout: https://vector-of-bool.github.io/2019/05/23/signals.html).
[1]: https://rachelbythebay.com/w/2020/03/07/costly/
[2]: https://blog.clubhouse.com/reining-in-the-thundering-herd-wi...
I'm using it with Janet for a side project, really liking it so far. Writing server-side html with a hiccup-like library is very nice! :)
(Clojure Hiccup library, similar libraries exist for other lisp-like languages: https://github.com/weavejester/hiccup)
In a multi-page app the complexity largely lives on the server, which is a known quantity, profile-able and scaleable. In a single-page app the complexity lives on the user's device. There are innumerable variations in device power, capability and so on so there is no good sense of what "good performance" even is. But it'll work great on the developers supercharged MacBook Pros, so the code gets shipped.
Just as important a practical difference (an outworking of the complexity location) is the matter of statefulness.
With an MPA, each page load is its own thing, generated from scratch from the canonical data source each time. Pages are essentially stateless.
With an SPA, the client will generally be interested in retaining data and caching things for at least the duration of the session, and it’s easy for state to get out of sync. That can go wrong so many ways, and cause so much pain, and resolving that commonly takes reloading the whole page, which is normally distinctly slower than it would have been in the MPA world. (Not that it need be noticeably slower, but it almost always will be.) Or the absolute worst case, persisted wrong data… that’s a nightmare.
Pro tech work really is 80% coordinating people (quashing jerks, dealing with drama, etc: “don’t leave the new guy alone with the CTO”) and 20% funtimes-computer-stuff.
It might not seem like that in terms of wallclock time, but it sure feels like it in terms of energy spent.
But as a developer, I agree that it is harder to develop these kinds of applications that I like than it used to be to develop multi page applications with mostly server side logic and only sprinkles of javascript.
I guess I just do, actually, think there is UX benefit to the complexity. But of course there are better and worse ways to accomplish it, figuring out that balance is the hard part.
I think a good rule of thumb is: am I mostly clicking or mostly scrolling? If I'm mostly clicking - say, something like AirBnB or Zillow or Discord - where I'm exploring things and interacting a bunch, that feels better to me as a rich client-side application. If I'm mostly scrolling, like a blog or substack or a newspaper or even twitter, that feels better as a website.
I thought of a good concrete comparison: Vanguard vs. Coinbase Pro. Vanguard is a website, and it basically works for them because their business model is very passive, but you wouldn't want to do active trading there. Coinbase Pro is a web application where you can do lots of exploration and take actions quickly, and for me, it would be a major hindrance if they implemented it in a traditional page-based way.
It's sad how many "web apps" (and even more regular sites) only work in Chrome - try using Firefox or any non major browser, and quite a lot of the web simply doesn't work.
Every page should promise some kind of plain text content length vs full page resource bytes ratio. Why would anyone emit 20MB of .min.js only to deliver 140 characters of tweet?
The business driver isn't just delivering the content, it's increasing engagement, and the SPA application provides a significantly better and scalable way to make that happen. Everyone seems to think they could do better, and we end up with yet another "modern" JS framework.
Multiple pages means multiple entirely separate client-side contexts that are a security hole if they can clobber each other.
The author makes tons of claims but presents no evidence.
Seems more about poorly designed apps and managed teams than anything inherent in either type of app.
Hotwire makes no sense: switching to managing client-side state on the server and sending HTML over the wire introduces a ton of problems: you are going to have to write and debug JavaScript eventually. Now your server is running client-side code to update every local browser state? How is that scalable at all?
React has a ton of great libraries for UI that these articles always ignore: Something like react-select to manage a dynamic dropdown- you can't implement that on the server.
You need SPAs for complex dashboards, and React is a great library to store and update local state. I don't understand what the controversy is.
I think Elixir is a super interesting case given how insanely fast it is. If your request to fetch new data and patch the DOM takes 5ms, who cares if it's slower than React? It's plenty fast for a ton of use cases and lowers complexity.
And then, if you do need the super slick interactive element, you bust out React and code up a nice little inline thing that writes it's value to a hidden input field that you can send along with a the submission of a normal form.
Webdev swung heavy to building entirely frontend solutions and I think the past year or so has been the potential beginning of the pendulum swinging back as teams are now feeling the pains of maintaining these huge frontend applications, on top of already needing to maintain the complexity of their backend.
Time will tell!
Even in the US people are still dealing with pretty bad internet in a lot of places. Even with the best internet, and assuming it takes 0ms to handle the request, 5ms sounds insanely optimistic. I'm in a big city and my ping (to my ISP's datacenters in the same city) is 10-20ms...
Another locus of optimization might be development speed and cost. An SPA is really two apps, a front-end, and a back end.
It costs 2x to do an SPA, and all things being equal, features come out 1/2 as fast.
Which wins the app with latency, or the app that gets features twice as fast?
Coordinating two apps is much harder than one app.
A reference for this topic is the Mythical Man Month, which concludes that communication overhead is what is expensive. When you have two apps, the humans, and the software both need to communicate and coordinate.
When there's two apps, you have an OR condition where if either app is broken or in disagreement about the expected API, both are broken. You have two sets of build processes, sets of tests, sets of files to serve, caches to manage, state to manage, versions to manage, and they all need to coordinate.
Instead of one thing to test, with an SPA, there's really 3! The server side app in isolation, the client side app in isolation, and the combination of the two.
Still, 50ms would probably be fast enough for a lot use cases, and if it's not, it's easy to configure debouncing.
The Browser manages HTML, CSS and JavaScript and provides Developer Tools to debug and manage these languages. When you build with a javascript framework, you package a user experience that is entirely run by the browser, only to dispatch and save to the server when necessary.
I doubt there is a future for "backend SPA" frameworks- no one will take seriously the idea of, instead of two REST calls to get and put data, every client must connect through a socket to manage user input. Surely that will dramatically increase server bandwidth, and ignite the same type of "bloat" arguments that SPA critics use.
Wow, thanks! I've been writing production apps in Vue and React for large companies for years now, but good to know I can stop. /s
I have nothing against JS; I have problems with complexity on teams of varying skill levels and tenure. If you're a JS shop, it's great. But if you're an agency of Rails developers building CRUD apps for B2B, you shouldn't feel forced to write a bunch of JS just to get some values updating on the frontend in realtime.
I've noticed that many people who enjoy developing SPAs believe that SPAs are the only way that web apps should be developed, and when anyone suggests otherwise, they get very defensive. Pretty weird.
LiveView can actually decrease B/W and load, because the HTML diffs are more efficient than sending JSON, and the statefulness means you don't need to constantly refetch user information from DB/cache, etc. The details depend on your app and how carefully you optimize it, but it's far from being an unconditional "dramatic increase".
> If you don't want to write JavaScript, you probably shouldn't be building interactive applications for the web.
That's gatekeeping and just plain insulting.
I've had great success with an LV app with minimal JS. The rendering is all server-side in Elixir, plus a few lines of JS hooks for the bits that truly need to happen on the client for a good experience: TZ conversion, countdown timer, table sorting, Chart.js rendering. No framework needed.
The user doesn't notice if a transition takes 100ms because LiveView is fetching an HTML diff from the backend, or 100ms because React is doing an AJAX query to a JSON API, it's the same end result.
>for experienced frontend software engineers
web development skill varies wildly. Much more so than in any other branch of software development.
btw, "backend SPA"? That web world has officially jumped the shark. "page" has no meaning outside of a web browser. backend single string application makes more sense than backend single page application.
My only, personal, issue with front-end is typescript - I don't enjoy it for some reason.
Not affiliated, but Inertiajs[1] has been a fantastic middle ground between MVC goodness and using React/Vue/Svelte for client side interactivity. It even plugins in with your Rails/Laravel errors for form handling.
[1] - https://inertiajs.com
[1] https://www.youtube.com/watch?v=860d8usGC0o&list=PL58Wk5g77l...
... management is just really stupid?
Yeah, that's a really really rare opinion in tech circles.
> You can make a great Single-Page-App with a User Experience that a traditional site will never be able to close to matching.
What is actually meant by this? What is an example of a feature that can be far better in a SPA than in an MPA?
Edit: And it's worth noting that this app is a labor of love by an expert web developer, i.e. the antithesis of a typical corporate project.
Hotwire is provided as an example of a Hybrid app framework / toolkit and is mentioned about five times. But so also is Svelte.
> (* I say ‘was’ because the instability of Basecamp’s management and the massive turnover of the team working on Stimulus and Turbo makes me wary of using Hotwire on a project. But given how popular Rails is, that’s probably me being overcautious. The principles they describe in their books, such as Shape Up, are still sound. The problem, as many have pointed out, was that they didn’t follow through in practice on what they wrote.)
Can't update original comment
A great example of a 'forced perspective'. The entire ecosystem of browsers, standards, devices, business imperatives is being forcefully turned into a spa/mpa comparison.