The year is almost 2018, and you are thinking about building your start-up. You want it to be modern, snappy, an app that your clients will love to use and use because they love it. You want to build a Single-Page Application (SPA) that will make it all better - for you and the user. It’s almost 2018 in the end, right? Not so fast, there are things you need to know first.
Modern way to build applications means absolutely nothing
You’ve been told that this approach is modern. “It’s the modern way”. That’s an opinion. Many of the applications you use on the web, many of which are fairly new - are not built in the principle of being a single-page application. “Modern way to build web applications” will mean very much different things to different people. There is no single definition that fits all.
There are many very bad SPAs out there, very few excellent ones
I can detect a Single-Page Application in most cases by just opening it. The pattern I observe is that things start appearing slowly on my screen, multiple spinners show up, then hopefully all the data will load and I can finally enjoy my awesome SPA experience. Or not. Quite often something won’t load. The usual fix is just to hit “Ctrl+R” which will refresh the page. This used to happen very often when I visited Heroku Dashboard, this still does happen on occasion when I search using the new Google page. My Facebook dashboard often gets stuck, especially when I am connecting from a poor quality Wi-Fi, and I have to reload it as well. These are, however, all what I consider excellent Single-Page Applications. They are way out of the league of most.
Many Single-Page Applications break the use of back and forward buttons. This feature, given you for free when you write traditional apps, has to be written programmatically by either developer or the framework creators. Huge majority of Single-Page Applications will not allow you to break the “loading…” of resources or pages either. Remember those 3 buttons next to your URL bar in the browser? Back, Forward and Reload/Stop. The Stop icon is not active when you click on a link within Single-Page Application. You can’t stop the process you initiated. It’s expected to load fast, but what if it does not? Enjoy the pretty spinner.
Many people, developers in particular, hate Single-Page Applications. I am not in this camp, but most of them do annoy me on occasion.
The applications that are not SPAs are everywhere, and many of them are simply excellent. Most media outlets will serve their content in traditional manner, many productivity apps (like Basecamp project management app, Github, Bitbucket) are not SPAs either, but are outstanding in terms of user experience.
SPA applications are often broken for a reason(s)
There is a reason why these applications tend to provide bad user experience. In fact, there’s more than one reason. Let’s list those one by one:
1. The effort to build solid SPA is huge
When you opt in for a traditional approach, your user visits a page, server does the heavy lifting of fetching the required data from database, putting it into human-readable format, and outputs HTML right to the browser. There are a few things where this can go wrong, but when you build a SPA - there are many more places where error can happen.
A SPA would load up the initial blank HTML page from the server. This
can go wrong the same way in both cases. But then, SPA would load up the
huge bundle of JavaScript code from the server, often measured in
megabytes. If it won’t load, user will get nothing. Learn to use
Ctrl+R
like I did to work this around.
Moreover, when the JavaScript finally loads, it needs to do all the hard work on client-side. In particular, it needs to load and process the data. This means making even more requests to the server. Before you do, however, you need to program the “I’m initially loading the stuff I need” state for all the components of your page that did request some data. Something you do not have to do at all in traditional approach, because the data is immediately present there.
Each of these requests can fail, the web is not a perfect place and our Wi-Fi networks certainly are not either. You need to detect it, re-try or leave the user with spinners spinning for infinity (the usual), or an error message (the usual “good” solution).
There are simply more places where things can go wrong. This requires programmer to write more defensive code to handle those cases. There is more requests to be made to the API - requiring more code to be written on the back-end as well. There’s more code being both written for, and running on, a very programmer-hostile environment: JavaScript engine in the browser. It is not hostile by design, but it is hostile because it was never designed to run desktop-like applications in it in first place.
The more code you write, the more code you have to maintain, and the more effort it will take to change the features you already implemented.
You might decide not to care about many of the above. But then you deliver bad user experience. Users will hate your app, and stop using it if it’s slow, won’t load the pages or won’t allow them to interrupt operations on request.
2. Front-end ecosystem is evolving very rapidly (and it’s a bad thing)
Projects I worked on over course of my career (since around 2004), spanned from a few days or weeks, to a few years. That is how long you have to maintain the code you write. When you are aiming to build a profitable start-up, however, you most likely want for your code to be used for years to come.
The thing is, JavaScript ecosystem changes so rapidly, it will be difficult to keep up. You will start a project in Angular, and after a year - suddenly - all the useful JavaScript libraries require you to use React. So you have to re-write all of your Angular pages now, because you want to use React, and react-router for the new functionality. And if you don’t - you are stuck on the obsolete version or obsolete library altogether.
The current fashion seems to be to use React, but what if that changes in 6 months because something revolutionary comes up? It did happen before and it will happen again. The front-end tools we used not just 5, but event 2 years ago are virtually unknown by now. Lead developers of Yarn, or even Nodejs decide to move on to other projects or even abandon the ecosystem altogether. It’s like you decided to build stuff with Flash all over again, just to learn it’s being deprecated.
Meanwhile, the server-side stack gives you reasonable amount of time it will be supported no matter if you choose Java, PHP, Ruby or Elixir to build your application with. Frameworks like Rails have been around for more than decade, React is only 4 years old in comparison - and it’s already very “old” according to front-end standards.
3. The application creators are not the top 1%
You can deal with points 1) and 2) above quite easily, by throwing a pile of money on your development team - the best engineering team on the planet. But chances are, most SPAs are not created by the top 1% richest companies like Google or Amazon, and they don’t have the best engineering team (and management team) in the world either.
Keeping up to date with rapidly re-shaping ecosystem of libraries, maintaining and updating more code - that you would not have to write otherwise - simply costs more money. If you are multi-million dollar company, you can afford that sort of thing on a regular basis but are you, really? Is your foreseeable development budget counted in millions, hundreds or tens of thousands dollars? You need to adjust your process and tools accordingly.
SPAs don’t get engineered properly because their creators can’t afford the effort needed to engineer them properly.
There is another factor here. The amount of information you have to digest, as developer, to properly build SPAs is also huge. You can observe many people would focus on only front-end or back-end development nowadays. Back in the day, when I was studying for my never obtained Master in Computer Science degree, my colleagues used to laugh at me that I was building web apps. “I write C++ (or Java), that’s serious programming” - said more than one to me. The students I talk to now, often try to avoid becoming web applications programmers precisely because it is a huge, complicated task to become one. It’s easier to be the “real programmer” of yesterday, than a good “web developer” today.
4. Testing is harder
Traditional web applications are pretty easy to test in automated manner. You spawn the application, prepare it’s state (database), then spawn a browser and click through making some assertions on the pages you see.
When you are building a Single-Page Application, automated testing is more difficult, especially in a functional, top-to-bottom manner.
Some people even argue you should not do that sort of testing anymore, because you have two separate applications that should be tested separately. Instead you end up writing and maintaining test mocks for the APIs in the front-end application, and maintaining artificial assumptions on the API usage in the back-end test suite.
The lack of proper integration testing of your application leads to components that work perfectly in isolation. You have no guarantee, however, that they will actually work well when connected to each other. There is no way to be sure the back-end will serve the responses the front-end expects.
In addition to the above, even if you decide to do the integration testing using something like Selenium, it becomes more difficult too in a SPA. Biggest problem, and source of race conditions, timeout issues etc. is the fact, that the browser is not aware of the “loading next page” state. It’s exactly the same issue that prevents the browser from displaying “stop” icon next to URL bar, that mentioned already. Instead, the tester needs to rely on set of timeouts, and hope that the mocked back-end responds on time. I found this approach to testing of being very much difficult and error-prone. There is hardly anything more counter productive and irritating than a tests that fail randomly.
False promises
I have heard many statements from SPA advocates, that turned out to be false promises in reality. Ideas, that sound great in theory, but somehow never work in practice.
One of my favorite ones is that “by building SPA, you build an API that later you can use with other clients”. This is a new version of the old concept from Rails, where your controllers could serve both: HTML and JSON/XML API with the same code. I have seen a working examples of both, but the reality is that your API clients will often have different requirements than your web clients. Unless the mobile app maps directly to the pages of the web app, chances are, you will have to write special API endpoints for it anyway.
The other one is that “the application will load fast”. If it loads at all, that is. And even then, it might feel slow and buggy as hell. I covered that already so I won’t repeat myself.
Third of my favorite ones, is that splitting back-end and front-end gives you amount of separation you can dream of otherwise. That’s a plain lie. While it is possible to dynamically load extra JavaScript on-demand, many Single-Page Applications out there do not do that. This is an extra effort, which - again - costs extra money. We usually end up with JavaScript bundles, containing 100% of application logic, templates and CSS (generated via JavaScript of course) put into one huge file that loads as a giant “hello user, let’s test your connection speed” for all our new users. That’s not modular at all. In addition to above, a modular code is a code that allows you to swap it to different code with ease. Try swapping some React code for some Angular code in your SPA, to see how easy it is in reality. When you have separate pages in traditional web application - there is nothing stopping you from using completely different stacks on different pages.
But I really really want to build a SPA
Okay, you need to be aware that the effort to build one will be bigger. Often, by an order of magnitude. Not just initial effort, but the effort needed to maintain and update the system afterwards too. Make sure you are prepared for the project to also go slightly more sideways, since there will be more code, more people to interact with, more points of failure - more things will go wrong along the way.
Find the middle ground
By now you must think I’m some sort of old dude, that’s grumpy, and shouts at the modern web (the cloud? :D). Not at all. I think there is a lot of greatness in the tools I already mentioned. I enjoy React, especially that it is easily embeddable in traditional, multi-page applications.
The approach I often take, is to rely on the traditional browser-server request-response cycle when loading new pages of the application. The form submits might be done via AJAX, the forms itself might be rendered by React. There’s client-side state kept in stores. But it’s not a one SPA: instead you have many JavaScript applications embedded into many different pages.
This gives us a few advantages. For once, we can avoid the majority of spinners and
loaders. The little hackish technique I use, is to pass the
top-level React components set of props
in data-*
HTML attributes.
The data is embedded into initially returned HTML page. This approach reduces the amount of
states the client-side components can be in as well, simplifying their
code significantly.
The other advantage is that you now have a much more modular code base. You can serve the page-specific JavaScript without much extra effort. You can replace the whole components on individual pages without affecting other components - on other pages. You can try out shiny new JavaScript library on a specific page, without a need to rewrite all the other pages. And if you do decide to do a rewrite, you can do it page by page.
Sometimes you may want to build a “hybrid” application too. Good example is PivotalTracker where the project dashboard is a fairly small SPA, allowing you to navigate to URL of individual user stories without page reload, but all the other pages like Settings, Profile, Account or login pages - are not part of it.
…but it’s going to be slow
Nope. Pair this with a fast back-end like Elixir Phoenix and you end up with a snappy end user experience, more resilient to network or runtime failures. In my experience, it’s more likely you can pull that off than building a snappy Single-Page Application.
Further reading
- Single Page Web Apps: The Worst of Both Worlds
- Why I hate your Single Page App
- The disadvantages of single page applications
- How Basecamp Next got to be so damn fast without using much client-side UI
Post by Hubert Łępicki
Hubert is partner at AmberBit. Rails, Elixir and functional programming are his areas of expertise.