If built without server-side rendering, they're often obscenely slow. Browse the Modern Web™ on 2G and it's rage-inducing. A comment I made when this same question came up recently: https://news.ycombinator.com/item?id=13212789
If built poorly, all of the browser features you've come to rely on break. Like the back button. And links. And scrolling.
Does your website need to be accessible? Better remember to build that in too.
How about SEO? AMP? Browser history?
Most of this stuff works out of the box with traditional HTML and server-side rendered pages. So even though it's 2017, I think the question you should still ask yourself is, do you plan to re-implement all of this functionality? If not, 10MB of the hottest new JS framework may not be for you...
(And this is coming from someone who builds SPAs for clients for a living.)
There are use cases where a browser delivered application is desirable, but you really aren't targeting "the public" meaning arbitrary consumers or business customers via a website. For example, there are good reasons to build browser based interfaces to certain enterprisy business apps for internal corporate user consumption. In these cases, you may be able to reduce the cost of maintaining full applications by relying on more simple and ubiquitous browsers while trying to keep some of the richer GUI functionality of a full fat client by building an SPA.
Nonetheless, your other points do apply (except for SEO and perhaps some of the mobile concerns, context mattering here). How you build them still matters; you just typically have greater control over much of the footprint and whether or not it's the right approach still needs to considered (SPA is not a default).
I appreciate that this isn't the typical area of interest for those coming to Hacker News, but it still exists and can be good to think of from time to time.
Now I've seen plenty of corporate tech management make those decisions on behalf of their user communities as well as vendors providing offerings to those same corporate institutions/managers which make that choice.
> If built poorly, all of the browser features you've come to rely on break
Alternatively phrased: "you may have to learn some new APIs"
But the SEO can be an issue (Google is not consistently executing AJAX) and render performance is definitely an issue.
I made https://www.prerender.cloud/ to address the SEO and rendering performance but that still doesn't make SPA the obvious choice.
Do you need a rich client experience? ( a UI that continuously updates to reflect the state of a data store, or more generally, a complicated UI that doesn't do a hard page refresh ) If yes, then SPA is the better solution.
If you just need a non-interactive set of pages (a blog), then a SPA probably isn't the answer.
> If not, 10MB of the hottest new JS framework may not be for you...
What hot new JS framework is 10MB? React is ~60kb gzipped
The auth frameworks (auth0.com or AWS Cognito) are large though, ~150KB gzipped. But with code splitting you can eliminate that from your main page. Or you can move the auth part of your app to a subdomain (app.example.com) and keep example.com lightweight w/out auth0/cognito)
Yeah, but you have to then call those APIs. It's like buffer overflows, which can be prevented with trivial checking, but those checks have to be done everywhere, which is why they're still happening in 2017.
It would be actively difficult to break back, links, or scrolling with server side rendering. Even on good SPA I run into bugs I have to work around on a daily basis.
Working on a SPA can be lots of fun, so I'd consider it for a hobby project, but I wouldn't go the SPA route for a production app unless there were some very compelling reasons to do so.
Just wanted to take a moment to thank you, and all the others who worked on it for making that possible. I know I wasn't alone to feel very, very lost and sad when it closed up.
So, free beer in case we ever meet IRL.
If you are simply rendering content and don't require high degrees of in-page interactivity, your traditional web page centric app will be much better user experience.
SPA's for content dominant sites are often a crutch to work around slow backend. A full page refresh (with optional pre fetching of next click HTML) is vastly superior to SPA's and spinners while loading. Browsers are good at loading web pages.
Browsers are really efficient rendering HTML, so it all depends on how complex your JSON to DOM element transformation is. With async parsing / loading of JavaScript, your new page can be rendered before the JS is even loaded, so the extra initializaion per page view is hidden from the user.
Which site feels better, yours or Wikipedia?
All my API is fully JSON up and JSON down(REST-like), and is like a microservice api. The server is a service with an API. The javascript client is in control of rendering the data it gets from the server. I'm convinced that's the most efficient design.
Since you say it's all JSON APIs, it should be trivial to build something on the server to server-render the initial page with content and serve that up while the rest of the application tries to download itself into the browser.
This approach was a commercial failure on the native platform level (those Office-like app suites with network install of components each time you needed them). It was also a commercial failure on the "build my desktop from scratch when I boot up" environments.
It yet another tick in the "does not belong in a browser" list.
Perhaps Single Page Applications need to run inside a window where there's no browser navigation bar, no url bar. Stripped down so far it's not actually a functioning browser. That feels very much like a native GUI library at that point. Except for that ludicrous notion of the app needing to be installed each time the chrome/window is launched.
I can't see how "install the app each time you land on this website" is competitive against native's "install this app once, and run many times" approach.
Why? Really seriously question. Assuming everything is cached and it is not some super large app what's the problem with that?
Github is a good example of it. Server side renders are generally faster than when done using front-end framework.
I will just post this tweet from a google employee here : In my tests, a client-rendered SPA, + caching, + service worker, is still slower than an uncached server render
1. They don't work well in multiple browser tabs. Specifically if you're storing a lot of data in memory it gets duplicated between tabs. Perhaps shared workers might be a solution in the future, but not now.
2. They allow designers to "enhance" the UI in new and wonderful ways, which isn't always good for user experience. It's like being tele-ported back to the windows native apps days. Plain old web apps forced simplicity and consistency in how things worked across the web. With spa's not so much.
3. Harder to test/re-create error conditions because often state of an application isn't represented in the url. (though it can, with good app state storage/structure, actually be better I think)
4. Harder to deploy new versions to apps sitting in users browser. So a user loads your wonderful spa app and leaves it open all day in their browser. You deploy an updated version mid day... how exactly does that user know to load the new version? In traditional web apps, updated versions are loaded on next page load.
5. You have to do something for page loading indication, since the browser no longer gives any indication it's waiting on the server for new data.
Unnecessary complexity: http://intercoolerjs.org/2016/05/19/back-to-the-future.html
API Churn/Security tradeoffs: http://intercoolerjs.org/2016/02/17/api-churn-vs-security.ht...
REST was a magical idea, but not for JSON & SPAs: http://intercoolerjs.org/2016/05/08/hatoeas-is-for-humans.ht...
Do you update only after the post succeeds?
The demos all use mockjax (great library: https://github.com/jakerella/jquery-mockjax) with an intentional slight delay to simulate a server round trip. Otherwise, it's just a DOM swap, which should be very fast.
The right tool really depends on your specific use-case, and business situation...
One of the apps I'm building has one page that needs complex interactivity. So that's the only page which will have a vuejs/vuex-driven UI.
For the rest, Turbolinks gives me high performance for (almost) free.
So for someone like me who doesn't like typing more, spending more time and fixing more issues than absolutely necessary, SPAs are a no go for 2017.
If you have a lot of experience writing SPAs and a team that's eager to learn, maybe you'll get everything right: server-side rendering, graceful error handling when the app plumbing fails, appropriate separation of server-side and client-side workloads, good performance on shitty connections, consistent behavior across browsers...
Personally, I enjoy using well-made SPAs, but often find myself fighting against many of them because there are so many considerations that go into making them right.
Even for older browsers, in "no-framework" setting, I have used https://github.com/mtrpcic/pathjs with a lot of success.
Getting scroll position to stay where you left is tricky, though. Has anybody posted some tricks on getting this right?
Maybe I should dust off my blog and do something about this missing bit of arcane SPA lore.
Hell even Google cannot get SPAs right using their own framework [1]
Like most people, I hate it when something like an article or a wiki turns itself into an SPA for no reason. But I do find myself thinking that the space between "should be an SPA" and "should be a static site" is not very big.
2) Javascript bloat. This is increasingly important as mobile gradually overtakes desktop, and cheap Android phones proliferate. The more client-side code, the more your performance is limited by your users' own crappy devices.
3) It's not trendy anymore. There was only one truly exemplary, popular SPA, and that was Gmail. Isomorphic/universal is the new hotness, and that requires server-side rendering.
4) The Stack Overflow Effect[1] didn't take. Every client-side framework is so painful that people can't wait to migrate their production apps to something different. So now there are dozens, probably hundreds of frameworks in production, and each community lacks the stability and size to allow newcomers to bootstrap quickly.
5) It's just bad UX. People still manage to get SPAs wrong in 2017. My bank just rolled out a new SPA interface to their suite of consumer budgeting tools, and didn't bother with the History API. Every time I navigate back, it takes me out of the suite entirely and I have to find my way back manually to where I was. I think this problem is a consequence of #1 and #4, though.
-------------------------
But if you're doing anything reasonably advanced or specifically need the "application-like" feel of an SPA, I don't think there are really any compelling arguments against it. Maaaaaybe the size of the initial payload, but there are strategies to mitigate against that anyway.
But on a super simple app (static site, basically), you have to deal with the overhead of the things that @aclimatt is referring to (browser history, routing, loading states etc) that basically come out of the box in your browser before you break them.
See: https://www.futureclaw.com
This also includes server-side rendering of the initial page.
I didn't use Angular or React or Ember or any other framework.. I just went ahead and made my own with ES6. The latest Javascript ES6 makes all of this easier.
If you're worried about complexity, just ignore the existing frameworks and do it from scratch.
Broken back button: check. Broken scrolling: check. Broken accessibility: check.
You'll have to weigh how much these affect overall user experience on various browsers and how it affects your business. I don't recommend people optimize for all platforms, because you will spend forever on it. It's perfectly fine to say "Ok, my site is only for iPhone users on iOS 10+ and Windows Edge 12+." or some limited subset of users.
Since I build so many SPAs, at this point I can spin up and work on a new one much faster than I could for a non-SPA.
<h1 class="big"><% =topic %></h1>
vs function createTopic(strTopic) {
var heading = document.createElement("h1");
var topicText = document.createTextNode(strTopic);
heading.appendchild(topicText);
heading.setAttribute("class", "big");
return heading;
}https://sergiotapia.me/using-webpack-and-react-in-a-rails-ap...
Best of both worlds.
The engineer in me loves the idea of them being separate. For performance, for technical reasons and for scalability.
The 9-to-5'er in me doesn't agree. I would spend too much time orchestrating. For example, saving user JWT tokens and having to handle requesting one for each api query. Too much time spent on things other than my core business problem.
With a simple Rails app, I can sign in my users with Devise, no brainer. And build out React stuff on HAML templates as needed.
Time to market is often a very important metric for a web app.
However SPAs make for a smoother experience. Most client facing apps live or die based on the user experience, and there are very few Product Managers who'd agree to a full old-style multi-page app.
As far as I'm concerned there are only two scenarios where I'll prefer an old-style app over an SPA:
1. when building internal apps for employees
2. when time to production is extremely important.
There is one part of it that is mildly interesting (on the site itself) which is the RSS reader capability here:
Also, edge swipe from right or left on iPhone completely doesn't work.
Compare your site to Wikipedia for example. Which feels smoother, especially on a mobile page?
A javascript application is like a computer program running in your browser. Whether you realize it or not, every page reload is a 'restart' of that app. Do you think an app needs to RESTART just because someone changes screens or soemthing? It's insane right? Without an SPA that is that is genuinely whats really happening.
I agree you can get into a slightly weird situation if you hit the back button after composing a new comment but your site would suffer the same.
BTW even hacker news has a 'back functionality' that sucks. You have to go back then refresh. Meta64 is already vastly superior to other online social media experiences including wikipedia and including better than this HN site.
Also, serious question: if I navigate your rss outline, how do I go back and chose a different section to browse? Right now you are stuck in the section you chose and the back button closes the app.
Perhaps, where this goes is that Single Page Apps and web browsers aren't a good fit together?
It exists in quite a few dekstop apps (explorer and control panel on windows to name a couple). Android also has an ubiquitous back button.
- Can’t open any of the pages in a new tab.
- Can’t go back.
- Can’t close the page without an “unsaved changes” prompt even though I didn’t interact with it at all.
- Doesn’t maintain state when restarting the browser or navigating through history.
- Doesn’t keep my scroll position when switching between tabs. Very painful if I clicked one accidentally.
- Doesn’t seem to be possible to focus or activate tabs via keyboard.
- Takes 3 seconds to load on my network. This is mostly up to latency, but making 119 requests totalling nearly 3 MB with no design to speak of yet probably doesn’t help.
- Takes a further 7 seconds to start fading in (why does it fade in?) on my device (2012 MacBook Pro 13") on a cold start, 4 seconds if just hitting Enter in the address bar.
- “Processing request” flashes on for a fraction of a second with an animated progress bar. How many progress states are there?
And it doesn’t even do anything in this state except let you flip between tabs. If things that are not this way are obsolete, I will gladly hang back and be obsolete with them.
But hey, the menu animation is very smooth!
> The architecture is state-of-the-art.
You should also probably back this statement up with something. Right now it appears to be a lot of self-horn-tooting.
But hey, sure, let’s spend the same half-minute to talk about the code. I open to a random TypeScript file – thank goodness you’re using TypeScript, because the JavaScript you’ve put under version control for some reason would make any non-vendor code in the language difficult to find in this time – https://github.com/Clay-Ferguson/meta64/blob/d9f3740118e8099....
declare var Dropzone;
Oh okay I guess we’re not using types here let config: Object = {
⋮
var submitButton = document.querySelector("#" + thiz.id("uploadButton"));
This… this isn’t how you reference elements in your “state-of-the-art” architecture, right? if (!submitButton) {
console.log("Unable to get upload button.");
}
submitButton.addEventListener("click", function(e) {
I guess the state of the art means you can’t use your browser’s debugger. Or it’s just for consistency given that you’re circumventing the rest of the browser too. this.on("queuecomplete", function(file) {
meta64.refresh();
});
What? I have no idea what this does (`meta64` isn’t declared, I guess because this file is allergic to modules. it also appears to be a singleton or global, neither of which particularly screams “good design” when named in reference to your app) but it looks like some kind of indiscriminate state update given that nothing is being passed to it. Maybe it’s not that, but no time to find out. We’re running out of seconds here.Wait, back up a moment:
submitButton.addEventListener("click", function(e) {
//e.preventDefault();
dropzone.processQueue();
});
Isn’t a major aspect of most of these fancy SPA frameworks that you don’t have to do this? $("#" + this.id("dropzone-form-id")).dropzone(config);
> This… this isn’t how you reference elements in your “state-of-the-art” architecture, right?oh no it is. Bonus points for kebab-case when the other two we’ve seen so far are camel. I’m nitpicking style because a meaningless literary device involving cars made me irritable. Sorry.
let ret: boolean = false;
for (let file of this.fileList) {
if (file["name"].toLowerCase().endsWith(".zip")) {
return true;
}
}
return ret;
That was a useful variable! I would write this: return this.fileList.some(isZipFile);
and then probably not write this in the end because it’s a filename check but we’re here to talk about architecture or something, not that: const isZipFile = /\.zip$/i!test;
`!` is a macro for bound property access in my state-of-the-art architecture. runButtonEnablement = (dropzoneEvt: any): void => {
if (dropzoneEvt.getAddedFiles().length > 0 ||
dropzoneEvt.getQueuedFiles().length > 0) {
$("#" + this.id("uploadButton")).show();
}
else {
$("#" + this.id("uploadButton")).hide();
}
}
I would think a F1 racer ninja rockstar architecture would be able to bind the visibility of this button to some state. More bonus points for not using `toggle()` and the same select-by-id antipattern. $("#" + this.id("uploadPathDisplay")).html("Path: " + render.formatPath(attachment.uploadNode));
Select-by-id again. I’ve suppressed the urge to gag by this point. `.html()` instead of binding this to some appropriate component? Nothing new. Mysterious global `render` due to declaring things allergy? Par for the course.Let me know if you would like a review of any other files so I can decline and spare my mental health thanks
You need to get off your high horse. Not even Jon Skeet thinks he creates perfect code.
Your project is nowhere near "great example of modern architecture in a web app". You depend on mysterious global variables everywhere, mix jQuery with HTML tags hard coded in strings (with classes and everything) and have no unit or E2E tests. I applaud your overconfidence though. Most people are ashame of their code, even when it's ten times better than this.
Serious tip: invest time in learning React+Redux/MobX or Vue. I promise you it will make your life much better once you realize your shortcomings.
Regarding the generating of the presentation code being done in pure JS/TS rather than a bunch of template files rendered on the server is actually another innovation in disquise. This app is so dynamic that even if it were template-based the templates themselves would be 90% logic, which is why just generating presentation code on the client is ideal. This IS mainly a client-side app, that consumes a server-side JSON API, rather than getting any actual HTML from the server. Yes it's inverted from the way the rest most people are still doing it, but what I'm doing is the future. Learn a bit about microservices and RESTfull stuff and you might begin to see the light, about how a browser can consume an API that sends back JSON DATA rather than rendered HTML. It's the future. I'm doing it right. You just don't get it yet. And if you're irked by my confidence, frankly that makes me happy.
> Learn a bit about microservices and RESTfull stuff and you might begin to see the light, about how a browser can consume an API that sends back JSON DATA rather than rendered HTML. It's the future.
RESTful APIs are great but you are 10 years late to the party.
Using RESTful APIs is exactly the same thing web services were back 15 years ago with XML.
Also, singletons are simply bad practice so that's just another sign that your code is far from "state of the art".
> Yes it's inverted from the way the rest most people are still doing it, but what I'm doing is the future.
Of course you consume JSON through a REST API and let the client handle HTML generation when building an SPA. "The future" you're speaking of has been industry standard for 10 years. Nothing radical or innovative about it. How do you think React, Vue or Angular work?
I think it was about line 8 in this file:
https://github.com/Clay-Ferguson/meta64/blob/master/src/main...
That's funny that you don't like singletons. Not sure how you got brainwashed about them, but they are good and VERY widely used today by all major codebases. Singleton is literally the "default scope" for all Spring Beans, so you are so completely clueless about that.
There are a few use cases for singletons but using them as globals to avoid passing dependencies around is simply bad practice. Your code is a tightly coupled and untestable mess at its current state.
It's funny that you arrogantly just dismiss every single person in this thread as idiots. You need to take a step back and consider that you might actually be wrong.
Having a naming convention is no reason not to do proper dependency management. This global approach just obscures things and makes it untestable.
The code will easily convert to modules if I ever decide to. For now I'm just keeping it simple. (i.e. no circular-reference risk, etc) It's easy to throw rocks at a design, but if you spent a day working in meta64 you'd realize how intuitive it is. Obscure is never the word you'd use.
I'm not sure why you would assume no one could find faults in the code when they even have comments right there about said faults.
So the mere fact that you said "giant global variable" is proof beyond any doubt that you don't know jack about namespaces, and all you were barely able to do was notice the lack of module loaders at the top of my files.
You may not even know this, but packaging an app into a single downloadable JS file (modules not even necessary) is actually the modern approach. It lends itself to better compression+minification. Once installed in a browser cache it's usable just like an 'installed' piece of software. But I thank you for the phrase "giant global variable". I'll be laughing at that for years to come.
Which is part of why I haven't used JQuery in ages. The last time I saw someone mention JQuery as a best practice example was arguably a decade ago, if not longer than that. If you are doing things by a book, it's not a book that was written any time recently.
At this point, any library I see that uses a global variable and doesn't support proper module loading is a code smell and a sign that the library is probably not well maintained.
«You may not even know this, but packaging an app into a single downloadable JS file (modules not even necessary) is actually the modern approach. It lends itself to better compression+minification.»
Actually, this has changed a lot in recent years, and your ignorance of how module systems work also shows up here. First of all, you can have compression+minification and modules. Even in the bad old RequireJS days when AMD loaders were state of the art there were good AMD bundlers. With modern EcmaScript 2015 modules (ES2015) there are a lot of great bundler options, Webpack being a particular darling right now, but there's also others.
This sort of bundling, compression, and minification is now seen as the last step in the chain, the job of the application/website developer and no longer something that every JS library under the sun needs to do bespoke. A big reason for this is developer experience (it's easier to debug when everything is a large collection of modules), but it also makes for a better user experience (the final application knows more precisely what modules it needs to operate [tree shaking], and in what order [optimizing bundles for specific use cases/first impressions]).
Overall, frontend web development is shifting to share modules with more backend operations and the node ecosystem's npm (and relatives that piggyback on it like jspm and yarn) has become the package/module management ecosystem of choice. This is great for developer experience, whether or not you are doing your backend development in Node, because you have an easier access to a larger ecosystem of libraries, an increasing number of which are "universal"/"isomorphic" running just as well in the browser as on a server running Node (or app running in Electron or an app running in Cordova).
Furthermore, for some of these platforms bundling+compression+minification isn't even necessary, such as an app or server loaded directly from the filesystem instead of shipped to a browser via HTTP. Even that is changing best practices because HTTP/2 mitigates a lot of the reasons that you even need to bundle in the first place (HTTP/1.x often can only handle a single file per connection while HTTP/2 was designed from the start to bulk download a collection of files in a single connection; the connection overhead being the biggest reason to bundle in HTTP/1.x).
There's a lot of great stuff to learn about the current state of the art with respect to modules and ES2015 and modern frontend development ecosystem if you thought to give it a try instead of sticking to old practices now long considered harmful (not just in JS, even, but in about any language: giant global stateful singletons like JQuery are a miserable antipattern any time they show up in any language in the history of programming).
I'm using Google's minification:
There's a lot of scattered Medium articles and "Awesome Lists" I've seen from time to time, but I don't seem to have any organized bookmarks on the matter.
I did not say bundling+compression+minification is "obsolete", I said things had shifted in how front-end development uses those things as tools in their toolbelt. It's a very different world these days in JS and lot of "must" best practices became "when needed" best practices. All I was trying to point out was that you don't start with bundling+minification+compression, you pull in those tools as and if you need them.
As for JQuery, I'm not the only engineer that sees it as a problem. It's the second sentence of the Wikipedia article summary on the Singleton pattern, for instance:
« There are some who are critical of the singleton pattern and consider it to be an anti-pattern in that it is frequently used in scenarios where it is not beneficial, introduces unnecessary restrictions in situations where a sole instance of a class is not actually required, and introduces global state into an application. »
-- https://en.wikipedia.org/wiki/Singleton_pattern
Google for "Singleton anti pattern" for many pages of articles on the subject, if you care.
2) JQuery: Any JS library is better when it adds only a single variable to global scope. JQuery uses $. My app uses 'm64'. Anyway what is the better DOM-manipulation API I should be using? Do tell. It has to be an actual competitor to JQuery (i.e. lightweight pure JS library with same DOM capabilities.)
3) Singletons: I use Spring Beans on the server side to be sure I have singletons 'correct', and I use TypeScript namespaces on the browser side. The most common way people screw up singletons is related to the instantiation issues, and circular-references. By using Spring @Component and TypeScript namespaces, I avoid the exact issues you are concerned about. Based on my architecture it's literally IMPOSSIBLE to encounter those issues even if I tried.
> Any JS library is better when it adds only a single variable to global scope.
No, this is how you use jQuery in 2017:
import $ from "jquery";
Simple as that. Just stop using global variables.What are your thoughts on for example React, Vue or Angular? Any reason you didn't go that route instead?
I have been waiting for React to emerge as a clear winner before using it. I only use libraries, APIs, languages, databases, etc. that I've concluded will still be around 10yrs into the future. I don't like Angular, and don't know Vue. If React doesn't replicate some future plans Google has for Polymer ecosystem i might try it.
> Based on your definition of "giant global variable" the $ in JQuery is also precisely that.
jQuery’s `$` and `jQuery` are giant global variables when you include jQuery as a <script> or by concatenation. When you load it as a module, it adds no globals, which is rather nice. Look at https://www.npmjs.com/package/jquery’s “Babel” section, which is also how you use it with TypeScript.
You also seem to be under the impression that polluting the global namespace is the only bad thing about globals. It’s one factor, but it’s also just generally an indicator of bad design. Organizing global mutable state into singletons and namespaces doesn’t help with this. You’re also crowing about TypeScript but throwing away many of its benefits by not using modules or typings for the libraries you depend on.
> You may not even know this, but packaging an app into a single downloadable JS file (modules not even necessary) is actually the modern approach. It lends itself to better compression+minification.
(ES6) modules actually quite necessary if you want your minifier/bundler to be able to remove unused code reliably. Closure Compiler can only do so much.
I think if an SPA can achieve all of these http://rauchg.com/2014/7-principles-of-rich-web-applications...
with minimal additional payload, then it's good.