A Bit of History
Back in the hazy days of 1999, Microsoft introduced an ActiveX component into Internet Explorer 5 that for the first time allowed Javascript within a page to fetch additional content from the server without reloading the entire page. These were heady days, and I remember writing a lot of Internet Explorer-only applications that leveraged this technology to load specific regions of content in response to user interactions.
We never considered writing a whole application like that however; navigation still fetched a brand new page from the server, causing a full reload. There were still multiple <SCRIPT> tags per page to load the different Javascript files required, and as this was before build pipe-lines, none of it was minified or compressed.
By 2005, the phrase Single Page App (SPA) had started to surface. The entire application could be loaded once, then handled by Javascript on the client. The only round-trips required to the server would be to fetch specific pieces of data used to generate content. 2010 saw the release of BackboneJS and AngularJS, two frameworks that not only provided the building blocks to construct SPAs more effectively, but also provide some organisation for the mountain of Javascript developers had started to write. In 2011 SproutCore 2.0 was renamed to EmberJS, 2013 saw the first version of React, and in 2016 the new version of Angular was released.
SPAs have come in for a lot of justified criticism, in part due to the heavy churn we saw in Javascript frameworks throughout those six years from 2010, and the fact that for a long while many of them offered an objectively worse experience than a traditional server rendered app. However, the last few years have seen a consolidation of ideas, less large breaking changes and a general maturing of the platform.
Are we now ready to see SPAs replace traditional server side rendered (SSR) apps wholesale?
Short answer: no, but…
Longer answer: read on Macduff.
You Broke the Web!
It’s fair to say, there are still those that don’t love the idea of SPAs.
Check out the film here.
See also Why I Hate Your Single Page App and The Disadvantages of Single Page App.
Let’s take some of these concerns one by one and see where the current SPAs land.
Critique
SPAs were supposed to be faster, but their first-load time is noticeably slower due to the amount of Javascript delivered and the time required to parse it. This is especially true on cheap smart phones which are important for the Next Three Billion.
Response
SPAs are guilty as charged, at least by some measures and implementations. They do deliver a lot of Javascript initially, and parsing time is slow, but there are some mitigating factors:
- Caching - The JS is a static file and can be easily cached by the browser. This drastically reduces the subsequent load times as the file no longer needs to be fetched.
- CDNs - Use of static assets like this is one of the big benefits of a SPA; they can be loaded from CDNs closer to the edge of the internet where content is accessed from.
- Code splitting - This splits up a single large bundle of Javascript into many smaller bundles that can be loaded as required. For example, not everyone may want the functionality associated with the /admin/* URLs, so why send it to everyone? Angular has been doing this route splitting for a while, Ember is close, and React has the functionality. Though true to React’s slim nature, the developer needs to wire it up themselves, so it comes at the cost of a significantly greater effort.
- Server Side Render, and then inflate the SPA - All of the major SPA frameworks now offer the functionality to handle the initial request on the server and return a pre-rendered page to the client, while in the background loading in and inflating the application state into the SPA. In many ways this is the best of both worlds, but right now comes with significant development overhead for applications with real-world complexity.
In summary, the first load time can be significantly improved from the ‘out of the box’ experience by investment of developer time.
Critique
More developer time? Meaning SPAs are more expensive to develop. If you consider the fact that you’re having to represent your entity structure on the client and the server, include validation code in both places, and manage permissions and security in both places, then it’s looking a lot more expensive.
Response
It’s expensive in different places. Yes, entities are often described in both places, but this is relatively trivial. Validations can be handled solely on the server-side under many circumstances, and permissions only need to control what the application shows – all the real security has to remain on the server. The fact remains however, that if you simply replace the server rendered pages with an API and add on an SPA, then the development costs are likely to be higher.
It won’t be by much, however. The improved separation of concerns between the API and client allows larger development teams to move much more quickly, and the component-based architecture of the SPA frameworks is a nicer, more composable experience. The framework structure provides clarity and organisation to the mountain of Javascript you’re going to have to write anyway.
Critique
SPAs may offer some improvements, but they create brand new problems. The ‘back’ button remains broken. New pages render half-scrolled, or the new URL appears a second or two before the content loads.
Response
If you write the SPAs in a best-practice fashion, then very few of these issues should be apparent. Which doesn’t change the fact that in an SSR app, these issues just don’t appear at all.
The truth here is that SPAs are a different technology, and different technologies surface different problems. At this point, SSR apps have been mature for nearly 25 years, whereas SPA frameworks are not even a decade old. That immaturity means that not all the kinks are ironed out, but the signs are that they will be.
Critique
Kinks like accessibility? SPAs typically have terrible a11y, a-tags without href properties, deeply nested mark-up, and no indication to screen readers when the content has changed.
Response
Screen readers often rely on the traditional “request page / reload with new content” paradigm of SSR apps to know that content on screen has changed. The navigation approach of SPAs, which simply changes the URL and injects new content, does not provide enough hooks for screen readers to work from.
On one hand, screen readers are going to have to adapt. Even within SSR apps, there’s enough interactivity in today’s web apps that requires them to know when content is updating outside of a full page load. On the other hand, SPAs should do more. All the major frameworks have fairly easy add-on modules that support an improved screen reader experience, and in 2020 Ember is focussing on improved a11y by default.
Single Page Apps – The Application for Your OS
Many of the SPA concerns can be addressed with a little extra effort. That’s the cost, so let’s take a look at the benefit – what is it that SPAs offer that SSR apps cannot? What is it that makes them a preferred choice?
The Paradigm of How You Build an App Is Changing
In the days of SSR apps, we’d write a monolith server, responding to all requests a user made with the appropriately rendered HTML page. When we started to switch over to SPAs, we replaced the HTML rendering views with JSON rendering APIs – but the fundamental server architecture was the same.
That model still persists, and is still the predominant way we write apps at Media Suite for a number of reasons, but times are changing.
Functionality is becoming more and more commoditised as Cloud Services step in and replace commonly required features. For example, we could use Google Firebase for data persistence and authentication, Stripe for payments, Raygun for crash reporting, AWS Lambda functions for custom API end-points, Google Maps for geospatial data, OMDb for movie information… If the apps of the future are more likely to be composed together from these various cloud services, then the SPA is perfectly poised to be a hub in the middle.
Sure, you could write a server rendered app that pulled from all these sources, but why would you? SPAs can do the same job and be served from any CDN static hosting source easily and cheaply. By using an SPA under these circumstances, the cost of your hosting drops, integrations are easier and you’re using the right tool for the job.
The jury is out whether these ‘composable, commoditised services’ will provide the necessary coherent experience for the significant and complex applications that make up the lion’s share of custom software development. Right now, I don’t believe they do. But I do believe we’re on a tipping point, and we’ll gradually see our services get smaller and more focussed, to be composed together via SPAs.
The Ability to Work Offline
The poster-child feature of SPAs is that they can really make the most of the offline APIs available through all modern browsers. Try to save while offline? No problem, save to a local cache and allow the user to keep using the application. When next online, the user can synchronise and resolve any conflicts that may have arisen.
The only real technical impediment to this functionality is the browsers’ inconsistency in dealing with how much storage space to make available to the web application; it can make saving large numbers of media files a little fraught. Yet we don’t all build offline as a matter of course, because it just isn’t a high enough priority to spend our development budget on it for many apps. As patterns and libraries continue to improve, the hope is that adding offline becomes a cheaper and easier proposition.
Better Handling of Loading and Error States
Ever seen pages like this?
These are known as ‘skeleton screens’ and can be used as a placeholder while content is loading. It gives the user a nice sensation of what to expect, and significantly improves the perception of page-load performance over waiting for a blank screen to fully render.
Having control over what is displayed during the loading state is a powerful weapon in the war with user engagement. Skeleton screens are one tactic, but there are others that can be more appropriate for specific scenarios.
Error states are also well accounted for. If part of the data fetch fails, then this can be handled far more gracefully than returning an error page. SPAs can determine the behaviour they want – they could attempt another fetch, return data from a previously cached version, or provide the user with a simple alert. Most strategies are also available on an SSR app, but are actually more difficult to implement. With the clean SPA/API interface, there is one place to handle failures.
Animations as a First Class Citizen
Imagine something like this:
This is a lovely UI pattern for log-in and registration, and you could do this in an SPA or SSR. However, if you wanted the URL to also change from /login to /register when the user pressed the “SIGN UP” button, then with an SSR you’re out of luck. The page will need to reload.
With SPAs, not only can we create awesome animations, but we can do them across route boundaries. This allows context to move from one page to the next, helping the user understand the interactions that are taking place. It’s what mobile apps give us, but better, because the URL is the key piece of information that unlocks people being able to share their favourite bits of the web with each other.
The Browser as an Application Platform
Back in 2011 when Scott Hanselmann wrote a famous blog post quoting Eric Meijer titled Javascript is Assembly Language for the Web, it was representative of a sea change that was taking place in how we view the web. It was probably early 2000s when we stopped writing web pages for pure content consumption and started writing web ‘applications’. By the 2010s we were expecting more complicated interactions such as drag and drop (Trello), inline editing without a full submit (Facebook), and auto-save (Google Docs).
Java Applets had tried this in the late 90s. The Java runtime was heavy, had versioning issues and was an ongoing security nightmare, but… they actually felt like fixable issues. The key problem however was that it just wasn’t the web. It was another technology bolted onto the side. As the web grew in power and capability, Java Applets fell away.
By 2010, Javascript was offering credible alternatives to Java, and with SPAs we had a web alternative to native applications. Even today, a good native mobile app is better than a good web app consumed on a mobile device, but that gap is closing as new browser APIs become available. And the web has a killer feature; it runs everywhere. Any device with a browser can run your app. In terms of bang-for-your-buck, if you don’t need the few features that only native provides then a web app is a cheap, adequate alternative.
In fact, it’s become a bit of a meme to ‘bet on the web’. I’m going one step further, I’m betting on SPAs. I’m betting that the internet will increasingly become fractured into small, specific services that can be consumed easily and cheaply from a web client. I’m betting that browser APIs, and the upcoming Web Assembly, will continue to bring a client web app closer to native performance. I’m betting that Javascript continues to evolve to meet the growing needs of developers writing bigger and bigger applications to run on the application platform that I’m betting the browser will become.
These are easy bets to make, because they’re already happening.
Jonathan is Director of Software Development at Media Suite.
Banner image: Photo by Christian Wiediger on Unsplash