Single Page Application Is Not a Silver Bullet
blog.bloomca.me
blog.bloomca.me
Anecdotally I find SPAs, while I'm more comfortable developing them, will take longer to build. You need to pay more attention to the quirks the author mentions and you need to spend more time explicitly optimizing your code. I like this paradigm because if there is inconsistent logic, poor performance, or other issues, it's my fault and I have the opportunity to fix it.
As a counter example to the ones mentioned in the above article, around 2 years ago I build a drum machine webapp entirely in JavaScript/React (https://io808.com). This has ~17 JS library dependencies and ~6000 lines of source code but the gzipped bundle size comes out to ~97 KB.
Just because you're building an SPA doesn't mean you need to spend less time optimizing your code, in fact it likely means the opposite.
Angular has lazy and eager loading, which reduces bundle sizes. It also doesn’t require so many frameworks to manage manually. They are still there, but they are sort of imported automatically by angular cli so it doesn’t take a lot of mental overhead.
Some issues I had, though, were with bugs in newer versions. Some of those issues stopped development outright.
Another issue I have on one team is the fallacious notion that the front end programmer will take over the front end so no one else will need to think about it. Business specific logic seems to be out of reach for my particular developers, which prolongs the development cycle.
The benefit of state management and inter component communication have to be balanced the the practicality of who is doing the work and if those people can take ownership of all associated areas touched by the SPA.
At least every time one of their members said the word async in the last couple of weeks/months, it's what they referred to, and it's a completely different (and unrelated) thing. I can't read your mind though, so I'm just guessing.
You'll get code reuse but that's trivial to get working elsewhere, you just save on redownloading shared code.
Likes others have mentioned, it's about the UX that you want to provide that dictates the SPA path.
- Content site = NOT single-page-app
- Application = single-page-app
Maybe there's more of a gray area between content sites and applications these days, but I think it's still pretty obvious: If most of the user's time is spent reading content, it's a content site.
If the user needs to look at multiple screens at a time (e.g. like a mail client or something of similar complexity), then they're a candidate for an SPA. If the user does not need to do that, the app is probably simple enough to not be an SPA.
You can do everything you mentioned without an spa just fine. Plenty of content sites (the majority, actually) survive just fine.
The idea is to load only the React components and the data needed for each route at load time. An example of a page generated with this technique: http://inflacao.org/series/
and you were right, although, with things like Angular Universal and initial server side rendering of SPAs in general, are changing this.
In practice I’ve found VueJS does pretty well in this regard. Rather than build a full SPA I can sprinkle a few components here and there on pages that are highly interactive. The rest of the mostly static screens on my app are perfectly happy to be rendered from the server.
It can be hard to strike the right balance between developer friendliness (ie maintainability and extensibility) versus user experience (ie speed and backwards compatibility) these days, but as always it’s important work to deliver the best user experience we can, and I agree with the author that SPA are not always the right choice in this regard.
In practice, I've found "hybrid" apps hard to pull-off. In my experience, the question becomes, where do you draw the line?
Take the simple/common case of presenting a list view. From the list, you want to allow the user to click to view details, which you then present dynamically--maybe in an overlay. It's a great user-experience. Everything pops, the user can easily return to the list view without a full page-load, etc.
But, you now have a details view that is not reachable directly via its own URL. So, you use the framework's routing capability to assign one. Now, things are getting weird, because you're routing a dynamic view on top of a server-rendered one. You'll likely end up finding that you'll have to route the server-view as well for general navigability. And, you also have to account for people hitting the details URL directly (i.e. render the proper underlying server view, then let the client route to the dynamic URL).
Not to mention the back-button.
All manageable, of course. But, it gets messy to maintain such a hybrid approach. By the time you've integrated the routing, etc. the question becomes why hybrid? Why not full SPA?
If your app is super-simple wherein you're just doing the tiniest bits of dynamic stuff and you don't need things like first class URLs, navigability among dynamic content, etc. then, yeah, maybe. But, that's not really a hybrid-SPA solution because there's no SPA there.
In fact, I'm not sure there is a such thing as "hybrid-SPA". By definition, seems it's either a SPA or it's not.
I'm open to hybrid being defined as two possible things: a regular SPA with server side rendering (SSR on manual refresh and first visit). Nextjs for react, Nuxtjs for VueJS, angular universal. I think OP was talking about the other definition of hybrid.
The second definition is for when your team prefers or is only trained in or is maintaining codebases in the legacy MPA style, typically similar to Rails+jQuery. When you need a specific div element to have advanced UI components and a jQuery plugin didn't fit the needs, the answer was "not happening". VueJS showed up and changed this to "can budget finite dev hours and willing to maintain", as it had good tutorials for assuming you wanted a small widget instead of a full rewrite to SPA. Do this enough times, and you have a hybrid. Now you have enough SPA-ness that you can be satisfied or you can plan a multi-sprint "convert the rest to SPA" project.
This one annoys me the most, but I will say an analogous problem is fairly common on non-SPA web sites, where reloading a URL doesn’t work as expected.
All requests below the SPA's root should be forwarded to the SPA root, which appropriately then routes the request to the proper views and controllers.
Proper usage of resource IDs in these URLs allows a newly initialized application model to populate and serve the appropriate content for the appropriate views.
It doesn't make sense to call something that doesn't respect that design a SPA. A poorly implemented SPA is what I would call that.
Hackers News '18, folks.
So it's absolutely about proper engineering to make sure pages have working direct URLs rather than just relying on other navigation while the app is already open.
With web pages as documents that only use JavaScript to “decorate” things, you get this for free. And it’s likely aligned with what users expect.
> Should a popup dialog be reflected in the URL state? Should a confirmation prompt? What about browsing a hierarchy of menus?
These aren't examples of problems with SPAs, they are examples of things that do not cleanly map to URL routing in general. You could ask all of these same questions of a normal web page and they would have the same answers.
No doubt single-page apps have their benefits, but there's an awful lot of new hotness kool-aid being passed around. The JS ecosystem is still suffering heavily from the inner platform effect.
Browsers can only navigate via URLs. Traditionally these pages are rendered completely on the server but SPA's can just as easily load the appropriate page based on the URL, it's just done on the client and requires routing to be setup.
Whether it's actually setup like that is up to the developers to do, as many don't use routing for all inner pages. If they don't put in that effort then there are no URLs to navigate directly and thus there is nothing for a browser to do to open a new window/tab. Routing is a solved problem and is purely about implementation.
Is this a serious question?
It seems like you're letting a bit of bias against the JS ecosystem invade your thought process here. There are countless solutions because they range from generic, such as a simple routing helper, to deeply integrated, such as react-router.
> rather than relying on built-in browser behavior
You mean the built-in behavior of navigating away from the current page? The single page application?
Is it? I'm new to web development, so I don't understand your statement. E.g. does any SPA framework offer support to the stop button? I've been learning Vue and Mithril recently. Apparently, both libraries/frameworks have no support for the stop button, so you cannot use the browser's stop button to cancel routing/loading stuffs after you clicked on a link.
Is react-router stable yet? I looked at react a year ago and every tutorial out there used outdated syntax because they revamped the whole thing. The last angular project I worked on tried 3 different approaches through the life of the project.
The point is, with web pages as just documents that come to the browser fully-formed, there's no need to ask the question any more: the URL is the path to the document, and you get a new URL when you get a new document. There's no need to make special cases for popups/etc since the answer is always "no". You just don't do routing logic on the client at all.
Edit: just to be clear about what I'm advocating, if you embrace the document-style non-single-page model, you simply don't worry about view state or how it's routed/represented: any javascript code you do write (jquery etc) doesn't need to touch it, and any "real" hyperlinks that load a new page from the server naturally affect the view state normally.
- Implement a non-SPA web app in Node.js
- Include (parts of) the server-side code in a Service Worker. That way, after the SW is installed, the SW would handle requests instead of the server.
- Polyfill the missing parts. For example, you could polyfill the database to store updates when offline and sync them later.
Maybe you could make a framework where you can share code between Node.js and the Service Worker, similarly to how you can do server-side React today.
> broken “back” button (sometimes it works properly, but in general people don’t trust it)
If your UX is good, the user will notice data is refreshed when going back. All current top tier SPA Frameworks have support for correct HTML5 routing.
> broken “open in a new tab” behaviour – people like to handle links in onClick handler, and browser can’t recognize it as a link (even if the majority of the links are valid, sooner or later you’ll encounter a non-link “link”)
Again, most up-to-date frameworks have a fallback by adding a normal href so you can still CTRL-Click it.
> sometimes broken “refresh” button – after refreshing you end up in a different UI (usually slightly, but still different)
I don't see a real disadvantage here, since you can simply store your state by manipulating the URL or even localstorage. So you even have the possibility to not store your state...But again, application design, not SPA
I do agree with TTI (at least on the first load) and bad performance on low end devices.
You also mention HTML5 routing.. Ok now you have to configure your web server to properly parse the URL and understand what it's conveying.
Client side routing? Have you seen the huge chapter dedicated to routing for angular? You make it sound like it's no work
Yes
> Client side routing? Have you seen the huge chapter dedicated to routing for angular? You make it sound like it's no work
Haven't seen angular's, they were late to the SSR party.
The worst programming is the result of choosing the wrong tools for the job. There is a class of jobs for which an SPA is entirely unnecessary.
I agree completely with the author; there's some things (games and such come to mind) which certainly deserve to be SPAs, but the majority of the time it's a horrific waste for every single visitor to have to process MBs of data just to display a few KB of text and hundreds of KB of images (at most). Why? Just so developers can show off that feeling of how awesome they are for using some incredibly complex framework. That, and the ridiculous rate of churn, are probably what most irks me the most about the whole web development community (and why sites like HN appeal to me so much.)
I'm not sure what's responsible for this "love of bloat", but IMHO we should be teaching developers how to do more with less, and not the complete opposite as seemingly happening today.
I think you're misplacing the cause into developers. Developer's want to use the SPA framework because certain magnitudes of "application like UI" become maintainence nightmares for teams who stick with jQuery or use youmightnotneedjQuery.
The reason they end up bloated or with random framework-supported elements broken (back button, etc.) is from what I call MVP culture. AIM and IRC were perfectly fine in the early 2000s, but instead of upgrading them for 2010+ they were abandoned or stripmined of value (Skype). Now we have slack, discord, flock etc.. releasing MVPs. Except apparently, they are perpetual MVPs.
Many industries are encountering this. Developers at large are not permitted to rewrite in native languages, optimize any non-blocking performance issue, fix bugs that don't affect the bottom line. Everyone's trying to disrupt an industry so they can vendor lock in high profit rents. When we finish switching from typescript to reasonML and decide to switch from reasonML to Nim/kotlin-native, MVP culture will still be there to tell developers not to fix the broken back button.
- Initial request is rendered server-side, so TTI is extremely low. Even if the JS hasn't kicked in, the website is already usable.
- Back button works perfectly fine. We use the HTML5 `history.pushState` method to add new entries to the history stack.
- Links are always rendered as anchor tags. So even if the onClick handler fails or the JS crashed, the link will still work.
- Compress the hell out of the bundle. Clocking in at 190KB compressed. (Edit: should do something about this, should be <100KB)
Basically, we have most of the advantages of rendering "static" pages, yet also get all of the advantages of an SPA. We can do pre-loading in search results for example. If you click a link in search results, we immediately render the page and use the data from the search result to show a basic page and then slowly enhance it.
This powers a large real-estate website that receives hundreds of thousands of visits a week. For this kind of business, a lot of stuff is important to get right. SEO related optimisations are extremely important. Hence, we server-side render everything to make sure the GoogleBot can read it all. The same applies to links. We keep our bundle size as small as possible to keep things speedy.
Oh, and I personally got a kick out of spending a weekend to make the website function perfectly well without JS.
I've been developing in Rails for the last 5 years, and am about to build my own products, and am opting for this kind of setup.. though have decided to hop over to Elixir on Phoenix, and will swap out Redux for Mobx. The application is mostly a dashboard (SaaS). Could easily be an MPA, but I want to move into more responsive UX, and wouldn't mind leveraging the portability for when I develop mobile or desktop apps.
It seems majority of these responses against SPAs are, as you said, all based on bad design, not because the architecture itself is fundamentally flawed -- though it is obviously going against the grain by breaking a lot of this functionality in the first place.
Having said that, the fact that browsers are providing native features to support SPAs, while bridging mobile-native features, is a massive sign of things to come. They're not going away, and support will only get better, and they will only become easier to develop.
Anyway, I'd be interested to hear about your custom router! Any particular reason you rolled your own? I was planning to just use react-router, but if there are headaches down the road in terms of managing a hybrid SPA with SSR, I'd appreciate the heads-up.
This I think is made worse by the unparalleled decadence in which the modern, first-world developer now lives. It is absolutely excessive to have to download 2.6 MB just to show a blog or some simple textual information, but thanks to broadband everywhere and terabyte hard drives, nobody under the age of 30 who isn't working in embedded systems thinks about this anymore.
Also I think that large bundle size being a mark against all SPAs is misleading. Yeah, angular is 1mb+, but vue is ~20kb, so the potential for the bundle to be small exists.
My view is the exact opposite. Whenever you go through a page reload you lose the state on the client. And that has to be there, as it's what the user interacts with. You then need to send to the server a state which is not, strictly, an application state (it's a UI state) and that state needs to be taken in account by the server, and possibly merged with the application state, when generating the new page. A mess.
On the other hand, a spa never loses its UI state, so you never need to recreate it. The server is also usually stateless, and simply satisfies the requests of the client. Basically you go from developing two separate applications with partly overlapping concerns to developing a single one, running on the client and relying on a service layer to query and persist its data.
I find the list of pros somewhat unconvincing.
For a lot of applications, a reload is no hardship, and is faster than most SPA interactions I see.
For a lot of granualar actions, jquery, while not sexy, is not harder to maintain than a full SPA would be.
The alleged development efficiency of decoupling front from back really needs context and argument.
As I see things, there are a lot of web projects where a normal app with a little progressive enhancement is a good solution.
I'll never get why we collectively decided that it's reasonable to grant every random website in the world execute permissions on our machines.
In terms of raw performance for content sites, I found hybrid implementations like Gatsby.js to beat most things. Most of the cons of SPAs can be significantly diminished with SSR, proper chunking, and a variety of other modern techniques -- it just gets complicated in a hurry.
This lessens the CPU power and bandwidth needed to initially load the page.
For technical reasons not worth getting into, on my current project, a SPA would be overkill and hard to implement. But I did take the lessons I learned from my time with Angular to create a poor man's model view framework with a combination of HandlebarsJS+JQuery.
After my experience doing it, I understand why a framework would be better. I'm reinventing the wheel (out of necessity), it wouldn't scale to multiple developers without becoming an ungodly mess without a framework to keep people on the rails and it would be harder to ramp people up. When I got thrust into my first Angular project it was easy just to read a few articles and watch some PluralSight videos.
As the dev lead, I will definitely choose a SPA framework when my next front end project comes up. For all of the reasons above and because it's easier to recruit and retain people when you're doing the new and shiny that can add to someone's resume - including mine.
It doesn't matter how I feel about the most popular frameworks, if I want to be employable, I need to keep up to date.
Or even better use a simpler solution that gives me 80% of the benefits of an SPA: Turbolinks, PJAX, intercooler.js or even a light sprinkling of good old AJAX.
Does anyone remember "progressive enhancement"?
As long as your backend is fast enough, it feels like navigating a SPA.
Writing jquery feels like idk...not quite like programming.
Organizing all your jQuery into meaningful classes and keeping the whole thing organized and orchestrated is very much the essence of programming. It's a rare skill (as a trip through many large jQuery code bases reveals).
Filling in some templates causing magic to happen behind the scenes on the other hand, not so much.
That being said, 1) are we interested in feeling like programmers or in pumping product? 2) if "pumping" is the answer, best tools depends on what is being done. All in favor or SPAs (when needed), but no, they aren't cooler than jQuery and are often big fat pigs that just shouldn't be used in that situation.
>Organizing...into meaningful classes and keeping the whole thing...orchestrated is very much the essence of programming
>Filling in some templates causing magic to happen behind the scenes on the other hand, not so much.
I'd agree and add that the different perspectives might be in large part due to the shift in programming away from procedural/code to declarative over the last decade or so. That is, if you started in coding over the last ten--and especially five years--you're much more likely to think of programming as declarative as much as code-oriented.
And, the shift towards frameworks is probably the biggest driver of the declarative trend itself.
I'm all in favor of what's fast and easy. So many jQuery messes have almost given me PTSD. So not knocking frameworks in any way. It's just weird to me someone would suggest using them is "more like real programming". No, it's more like one of those paint by number kits rather than a blank canvas and some paint. Of course many people are going to get better results with the paint by number kit, but it isn't more like "real" painting. It's less like "real" painting. And, if you reach for a paint by number kit when all that's needed is a few red dots you are messing up and ought to learn the craft naked a bit for such situations. jQuery isn't a paint by number kit. More a set of stencils and brushes you are free to make as big of a mess as you like with.
Ha! Only because I can totally relate my friend. Been in the game a while myself. That really is how I can identify with what you're saying.
And, you know what? We're right! Adding an attribute to an HTML-like component tag is not programming, no matter who insists it to be so. That's not to say there's no value to such approaches, but it still doesn't make it coding.
Was talking to another coder friend a while back and we agreed the game has changed: it's more about integrating pieces into a whole than writing swaths of logic. In other words, "coding" is now more assembling than construction.
It's possibly (but not categorically) more productive and it may represent "progress" by some measures, but it is still ironic to hear that referred to as programming over actual programming.
1. tons of widgets/slides/inputs in one single page
2. click submit -> error -> go back -> all filled info gone
3. cant open links in new tabs
4. cant copy shit off the inputs because text was in some kind of fake div with v-data attribute with disabled cursor movements. Had to use devtools to copy shit
5. Naturally, cant paste shit. Had to manually type MD5 checksums.
6. Always got redirect to home page after login
7. Broken links everywhere. Cant restore page state via link. IDK if this page is broken or still ajax in-progress. Had to open devtools to see if there's any js errors
8. If page is broken, refresh, boom, all your typed contents and procedures are gone.
I think if people cant make a proper "multi" page form based web application, they should not be allowed to engage SPA at all.
I was very surprised to read that.
Very often validation and context-sensitive behaviour needs to be performed on the client and (since you can't trust the client) on the server. You can certainly argue this is better user experience: you don't have to wait for a server round-trip before finding out an action is invalid, but it's hardly decoupled.
Isn't this a part of why node is so popular as a server platform? The stack is now so deeply coupled, it is very beneficial to reuse code.
The same application loading and doing things instantly as server-side templates feels comparatively cheap and un-modern.
What is wrong with me?
Do you ever find some animations to be lower quality than others? E.g. a pop up with a progress bar being lower quality than a spinning wheel? That might be another sign.
Why is there such a strong tendency towards treating things as a this-is-it(-this-time-for-real) thing? This only confuses people and muddles up expectations.
You could also use session like you always have if the SPA is being served by the same server.
The big issue I have with JWT is they are hard to invalidate for security reasons, but if you lower the validity and refresh the token more often you can mitigate (not eliminate) the risk.
We use Vue + Firebase and even have SSR working w no complaints. (So it's actually more than just a SPA)
Or at least, no you shouldn't make a site that isn't SPA. With universal rendering, light frameworks like Preact/Inferno.js and things like microjs.com , you can make a site that loads faster, renders faster and navigates faster then a multi-page application. You can make SPAs behave like a regular site - with links that work and all.
It's simply that many developers/managers don't care for or know how to make fast websites with SPAs, but then again even Multi-page applications are usually bloated with dozens of marketing, analytics and third-party libraries which makes them just as slow.
I don't think reloading the entire page on every request is acceptable in 2018, even if it's a blog.
I love me some jQuery noodles! For some sites it becomes unwieldy i'll agree, but when sprinkled it provides a lot of enhancement.
SQL Server has had check constraints since at least the year 1996.