Why I hate your Single Page App
medium.freecodecamp.org
medium.freecodecamp.org
- Every click is a full page load, and you still didn't manage to get caching right, so it takes 2 seconds to load.
- When I have multiple tabs open, your badges and notifications fall out of date, because you never poll for updates.
- If I mis-enter information into a form, your error handling forgets all my information and I have to re-enter it.
- You forgot to update the styles on that rarely-used page, and now it's got half the new CSS and half the old CSS and the point is the header runs halfway down the goddamn content in Times New Roman.
I get it, there are problems with SPAs. But this perfect land of one-page-per-link has real usability problems if it's not implemented properly, and there's a lot of maintenance overhead associated with a sprawling mass of transcluded templates. If we're going to criticize an architecture because it's easy to poorly implement, I don't think "ROCA" or whatever we're calling it has any meaningful benefits over SPAs in the general case.
Why I hate your SPA:
- If I click on a link, I receive no indication that I actually clicked a link, then 2-10 seconds later the page abruptly loads. Sometimes the page doesn't load at all.
- I cannot tap and hold a link, wait a second for the pop-up menu, and open the link in a new tab because the link isn't actually a link. It's implemented in JavaScript.
- The back and forward buttons don't work as expected because either history isn't maintained or it's shoddy (scrolling should not be a history event).
- The refresh page button don't work as expected because it resets you to the beginning.
I can't think of any good reason why the browsers can't help out with this UI / UX. Am I crazy?
Clicking on a save button and seeing the page hang is the normal way browsers handle http requests. But a javascript-enhanced experience can tell users about the local perception of the http request (ongoing, timeout, retry, error), or it can inform the user that it's safe to navigate away while their requests retry.
Edit: also agree to it disappear when accidentally hit back or leave page... I don't think you need to ask permission to use cookies so should opt for it... Or some other way to store.
Interesting about the separate window, thought cookies could be set to never expire.
Cookies can certainly be set to never expire, but you don't know which window the cookie was intended for because the cookies are shared.
[1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/loca...
[2]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage...
I finally used memcache(d) so that's cool.
Funny that you mention it. Loosing form data on page refresh or clicking "back" button was a problem on the Web until browsers started keeping form state along with page history. Suddenly millions of websites got this new feature without actually doing anything differently. But that was only possible because page transfers were semantic. Frameworks of that time that tried to manage their own form state (like ASP.NET WebForms) didn't have real page transfers, so they didn't get this "upgrade" and currently are significantly worse off in terms of usability.
I think there is a lesson here.
1. Every click is fetched and rendered faster than your javascript code has had a chance to execute, let alone fetch or render.
2. True, minor headache though compared to the vast improvements in usability.
3. Nope.
4. Nope, this is a bug - something javascript pages most certainly are not immune against.
Note that I've of course seen countless slow, bloated static pages - but that's pretty much never because of the tech stack - but because of bloated css, silly large images, and often horrible convoluted html.
Hn is of course a typical example of a web page that on paper would be great as an spa, but works fine as a static page with minor js (broken up/down links notwithstanding..).
D-lang forums is another :
Assuming a 64 kbps connection, and a 50%+ gzip compression, downloading a couple of pages of text is done in about half a second. If the user has such a terrible connection, they've hopefully turned off image loading. I don't think adding 40-400 kbs of gzipped js is going to help with the speed...
It also doesn't work well when opening lots of tabs (which is a popular technique that helps immensely when browsing on a slow connection as pages are loaded in the background).
And if you have a really slow or spotty connection the javascript page would barely load at all.
Also if I have to use 64kb I completly disable js in my browser and whitelist just a couple of websites - best adblock on mobile.
Unfortunately connection are more intermittent now than ever before.
If you turn off images, you get in with 12kb of data. I think 12kb of data should be transferred faster than even the more lighter JS frameworks around...
Even when I was coding web pages in Perl in the 90's I wouldn't do that to a user, that's just mean. You don't need a SPA to provide proper error handling that also rebuilds previous state.
In fact sometimes their web form just forgets things for no reason.
There is no competitor in sight because of the huge huge SEO juice they have built over 15 years.
But it's not THAT inconvenient!
> You forgot to update the styles on that rarely-used page
That's solved with proper css, not SPA.
Anecdotally, every team I've worked with has converted to a SPA, and the main ergonomic upgrade is that updates to the signup page or the "not found" widget quit getting forgotten whenever we change tweak our design. There always seems to be one page that's out-of-date for 2 months before anybody notices, then it quietly gets fixed up because we forgot that the design tweak was a CSS+HTML change, not pure CSS.
Not that that's perfect -- once you have a SPA, nobody remembers the 404 page, because it's a different HTML base...
This was fixed in browsers years ago when they started keeping form state along with page history. I can refresh this page, go back and forth in history, and this text box will keep my comment. Ironically, websites that tried to be clever about form state management and faked page transfers (like ASP.NET WebForms) didn't benefit from the fixes and currently are significantly worse off in terms of usability than websites that used vanilla forms.
I think there is a lesson here.
In about 95% of the SPAs I've tried, each click takes even longer than that.
"you never poll for updates"
Yes, because apparently AJAX/AJAJ is completely nonexistent and there's absolutely zero ground between "pure-HTML like it's 1991" and "VueWangulaReact.js single-page monstrosity".
Hell, even a meta-refresh will fix that if you really do want "pure-HTML like it's 1991", though that's certainly inelegant.
- The author is describing hypertext, which is perfectly fine for textual static content, one of the key goals for which the World Wide Web was conceived.
- Server-side generation of HTML seems totally out of place for other use cases: HTML/JS are now the assembly languages of the web and they belong in the browser. Besides a more distributed architecture is just perfect: server and client focus on what they do best.
- Fixing web-apps with broken history buttons and unlinkable urls is important, but to be honest there are fewer and fewer SPAs that are mono-url these days. And it's perfectly fine to have a unlinkable "Delete this Invoice" app button. If it's not real content it should not be a link.
The first two are fair criticism but they aren't nearly as detrimental to the user experience as the typical single page app antipatterns (no history, no indication of page loading, refresh button is broken, etc).
While it is of course possible to execute any design well or poorly, the observation that a typical single page app is worse than a typical multipage app is certainly valid.
This. I have a very short list of architectural must-haves that I use when evaluating junior dev's code at the start of a project, and not having robust error handling that preserves submitted form information is right up near the top.
It's an annoying design mistake with two input elements, unworkable with five, and just plain cruel beyond that.
Just handing someone an expanded version of "implement good error handling" is not likely to result in a good product. Every project has nuance and subtle trade-offs -- the code review is an opportunity to teach juniors how to map abstract design principles onto concrete implementations without falling back to "cookbook / cargo cult" programming.
I want them to take ownership of their own code, and advance down the path of craftsmanship because I believe this results in long-term value -- both to them personally and to the project at hand.
"Hey, here is your test and here are the answers. You've got one hour" :)
This really shouldn't be a problem for anything other than file inputs. If someone's form is losing submitted information on a validation failure when using an actual HTML submit, then I find it pretty unlikely that they're doing a good job of AJAX submission.
How bad is that really if users are aware of it and they can simply reload the page?
Of course, chat/messenger is something else, but I consider that an exception. (It's easy to build a chat service on top of a static website).
My SPA is different - https://www.seasonalfoodguide.org.
Browser back button works like normal.
Each screen has its own discrete URL, so you can share links, refresh, bookmark or open in a new tab, just like any other website.
I think the criticism here is misguided. The problem is not with SPA as an architecture, it's with SPA devs not implementing features that end users are accustomed to on a website. They can all be added, it just takes effort.
The biggest valid criticism to SPAs IMHO is the initial site load. There are ways to mitigate (CDNs, progressive loading, compression, etc) but pound-for-pound SPAs probably have longer initial load times on average for the first page than a vanilla HTML site does.
Once it's loaded there's no faster way to traverse a website, IMHO.
The back button normally preserves scroll position. Your site takes me back to the top of the list.
History transitions are especially jarring in Safari. It optimistically restores the viewport state, so at first the page appears to be loaded but doesn't respond to clicking or scrolling. Click events register before the page is repainted, so the user can end up on the wrong page. After that, the list appears to jump to the top.
As other people have mentioned, some ways of opening a new tab work, and others don't.
> Once it's loaded there's no faster way to traverse a website, IMHO.
In my experience, this is almost never true. SPAs are good for some kinds of interaction, but traversing collections of resources is what web browsers are designed to do, and they do it pretty well.
The author's criticism is that people often build SPAs based on theoretical benefits and don't recognize or don't put in the effort to fix the practical shortcomings.
This app is a good example of one of my pet peeves: ctrl-click doesn't reliably open links in a new tab.
While the rest functions very well, this is case-in-point ... it's difficult to re-invent app use cases that browser vendors generally take care of.
Edit: Printing is also borked it seems, whether or not that's a use case for your audience is another question.
Right click and open in new tab also works. I always forget about ctrl-click since I never use it, odd that it doesn't work given how well everything else does!
It doesn't work in Internet Explorer though. No FF on this machine to test out unfortunately.
That is an incredible website. It is amazingly responsive and fast, I couldn't tell it was an SPA because of how well it behaves in every possible way, yet it is far too responsive to be anything but an SPA.
Even as someone who cooks a lot, using the site doesn't come naturally to me, my typical shopping method is to go to the store and think up of what I want to cook as I stroll around the outside picking up fresh food. Or I put recipes from blogs into a shared family OneNote section and use that to do more directed shopping.
That said, the links off of the food pages are high quality, with very good info on what to do with each item, so I try and spend some time exploring the resources you've put together!
This actually illustrates his points. Which is this: the browser(s) ALREADY implement all of those features. It already knows forward and backward and new tabs and link and etc. etc. etc. SPA's completely ignore these and must re-implement everything. This means: * choppy differences between different SPA * when new browsers come out with new navigation features, guess what, you have to reimplement them! * Extra javascript code * Extra bug opportunities * What if a user keeps your page open so long that you change JSON? I have seen my wife keep a tab opened for weeks....
I guess this is just my DRY training kicks into overdrive, but reimplementation of well-tested, well-proven patterns really doesn't smell right. Which was his point.
This, in combination with the fact that developers don't take the extra effort, is exactly what the article is complaining about.
The problem is not that SPA devs are not implementing these features, it's that the devs are responsible for implementing them in the first place. As other people have noted, even though you have gone through great lengths to make your site work as expected, "open in new tab" does not work.
These are browser features, and devs shouldn't have to worry about implementing them.
Only one data point, but 15 seconds to load here. After that, yes, everything is, as advertised, snappy.
The biggest things for me are probably the excessive amount of resources used, the long load times, how hard it is to get things "right" (like not breaking the back button) and links taking awhile to load because we have to load your stupid SPA (if they work at all).
SPAs did win out big in one area though. On mobile. Except on mobile they're called "apps". As much as many HNers like to chafe against native mobile development as they yearn for the open standards of the Web, native apps have basically "won". Users love em.
I guess that leaves desktop.
I do wish more people asked "does my Web presence need to be an SPA?" or even "will it benefit significantly from being an SPA?" because I feel like the answer more often than not is "no".
Why would links take longer to load in a SPA? CSS, Javascript and the like should already be downloaded, there should be much less networking involved.
Also. My SPA's come in at around 120-130kb minified (no gzip). That's about the same as "pure-html" sites which include jquery.
I do agree with your last point though. Pure content sites, or even "mostly"-content sites, is probably better off with backend generated html.
react-dom is just a touch bigger than that alone, fwiw
You can wrap your SPA in a native app and thus cache the requests ahead of time. But develop on the web, and use Cordova.
[1]: https://sdegutis.com/blog/2016-03-21-why-i-dont-use-clojures...
Does it matter though, if they are still all prevalent on single page apps?
to be fair though, as with everything there are pros and cons. Once i learned to deal with some of complexities and crazy UI/UX, using our web app is like driving a spaceship and it does work pretty fast considering all the data getting lifted, html getting rendered etc.
- deploy headaches
- the infamous JS fatigue
- debugging (minified code and nested anonymous functions make it painful)
- troubles with content blockers
I guess we'll have to wait a few years until the JS/SPA ecosystem will become more mature and find good solutions to these annoyances. Until then I'd gladly stick with rails, django & friends, both as a developer and as a user.
That reminds me: if anyone out there has any advice how to make AppCache play nice with JavaScript-based routing solutions (say, react-router), please let me know.
Modern users want content delivered to them in a personal, rapid, contextual(stateful) manner
Fight.
Is that actually true? Maybe it's that web developers want to deliver content in this way and try to do it with the browser instead of a native application more fit for purpose and they don't actually give a damn about what the user wants.
Remember old forums that made you load each and every reply in a thread?
Compare that to Reddit which allows for collapsing and expanding of threads, and for async loading of reply chains that go on for too long.
Being able to open-in-place media users link to was a huge reason for the uptake of Reddit Enhancement Suite, now, a feature built into Reddit. Upvoting not taking users to a new page or reloading the page (compare to how Slashdot used to do it!), all these features involve maintaining state.
Same thing for most bug tracking apps. If I am triaging a list of bugs, a SPA is the way to go, I don't want to spend more time on page loads then on marking triage status.
All these concerns assume HTML 5 history api doesn't exist. It does and SPAs can do all that.
Stefan identifies common pitfalls or shortcomings of most single page applications such as:
1. Bad performance because of too many operations
2. Breaking browser navigation buttons
3. Not having unique links/anchors to in-page content.
While there are in fact multiple ways of bringing focus and solving these problems the authors suggests that simply ditching SPAs for native toolkits or backend/frontend system is the way to go.
While switching away from SPAs might mitigate the problems listed above - it will also present new problems that developers have to tackle - or simply spend time on.
The main problem here is how hard the problems he describes are to deal with in current frameworks I believe. He has one thing 100% right - bringing attention to those problems is the right way to go.
This is the design our team likes to follow, I haven't seen it documented anywhere (but I doubt it's new), so I'll write it here.
Essentially, we try to replicate traditional server side MVC thinking on the client side. Decent server side rendered apps that don't have the problems mentioned in the OP's post usually follow RESTful principles to some extent. This means that what you see on a page is a pure (ish) function of these things:
* The parameters in the URL
* The state of the database
And nothing else. Translated to the client side, it means that you want anything the user sees to be a function of: * The URL
* The state of the model layer (or the flux stores, whatever)
* Addendum: 100% of the model layer is an
eventually-consistent subset of the database
We use one exception to this rule: unimportant state is allowed to be right in the view (we use React, so that's component state). Stuff like "is the dropdown menu expanded" or "which of the items in a list are selected" is neither in the URL nor in the model layer (because we won't sync it back to the database). The rule of thumb is "if the user refreshes the page, is it a problem if this data is lost?". If the answer is "no", we can make it component state, as close to the action as possible (so not in the root component usually).All of this combined gets us a lot of stuff for free. For example, all our modal dialogs, and even "is the menu shown" is addressable from the URL. This might feel like overengineering, but it gets us lots of stuff for free.
For example, users on mobile phones expect to be able to close a modal or a menu by hitting the hardware back button. We get this for free, because when the user does an action that causes a modal to show, we redirect to some URL like example.com/wherever/?someModal=1. (all using the history api). Router picks it up, modal is shown. Model layer is not touched, this is 100% view layer work. Then, when the user hits "back", the browser restores the URL to example.com/wherever, router picks up the change again and the view renders the same page but without the modal.
We get all the usual browser features for free simply by following basic REST ideas. Don't put URL stuff in your stores. Don't put non-backend-synced stuff in your stores (just like you wouldn't put important state in your sessions in server side rendered apps). Put everything that matters in the URL.
Any routing library that encourages you to sync URL stuff into your model layer is, IMO, wrong by design.