It’s been already 11 years since I wrote my first SPA (Single Page Application)— although back then they were sort of known as RIAs (Rich Internet Applications, as if there were other apps that didn’t run on the Internet!). Also back then, there were no JavaScript frameworks available to implement the MV* pattern on the browser so I had to do a lot of custom plumbing using PrototypeJS and some bits of JQuery. Fun stuff (let alone my backend was IBM Domino)
In contrast, back in the day the development community had plenty of options to implement the MV* pattern on the server using enterprise, high-level language and frameworks. Java and Struts were all over the place when it came down to build Web Apps. This also meant less JavaScript, less micro-interactions, less fresh user interfaces but more stability and productivity.
However, the dilemma already started around that period of time: either you write your App leaning on lots of JavaScript and client processing (the technical side of Web 2.0) or you go with the flow and build your App using a wide-spread, server-side rendering framework. Well, I already told you my choice!
(Note: back then it was not even called SSR (server-side rendering). Why? Because as I said, there were no client-side rendering. I guess this concept popped recently just to make a clear differentiation when the latter was also becoming a reality. Due to this, SSR is seen today as a feature of a front-end framework and not a term used with traditional back-end frameworks)
SPA and SSR
(Before continuing, let’s set the basis for further understanding. Let’s asume here “rendering” refers just to markup generation and DOM construction, not just the fact of visually displaying the data)
SPA is an application that runs on the browser and does not require page transition or navigation to render new content. Data is dynamically retrieved from the server (using XHR technique) and processed at the browser to display the user views. This is, client-side rendering.
SSR is the capability of a front-end framework to generate page markup on the server and transfer the generated user views over to the network to the browser, which will just display without any further processing. This is, server-side rendering.
Having said all that, here we are today presented with a new shining choice: you can either write your App with client and/or server side rendering using the same language and platform: JavaScript. And this is a tremendous step forward. You can write a fully responsive, fresh user interface with all the dynamic refresh and overall desktop-like behaviour (SPA) combined with server-side processing for generating the user views with data coming from third-party APIs and WebServices (SSR)… using the same JavaScript framework. These type of applications are also known as Isomorphic Apps (deprecated term) or Universal Apps.
Universal App = SPA and SSR
I’ll talk briefly about what choices do we have out there, focusing on the largest two players: React and Angular. And we’ll see how I am a bit disappointed with the latter.
React Framework
This popular front-end framework created by Facebook provides top-notch, built-in capabilities to build Universal Apps combining the best of the SPA and SSR worlds. Any developer using React framework can generate mark-up on the server (with the obvious positive impacts on performance) and then leverage all the SPA capabilities and functional components.
Angular Framework
Angular Universal (oh the irony) is the framework developed by Google team for those developers who want to incorporate SSR capabilities onto their Apps. It was first released as an independent framework but, since Angular 4 release, it is part of the core platform. It provides the necessary mechanisms to write some Node.js code to process browser requests and generate Angular (Material) components and markup on the server.
But can we really build Universal Apps with this framework? What type of SPA — SSR choice is it when we we are using Angular Universal? I think it is an exclusive one. After some experimentation, I’ve learnt that you have to pick either SPA or SSR approach for your App, but not both at the same time. So if you decide to include SSR capabilities for your App using Angular Universal, you need to be aware that this comes at the expense of sacrificing any SPA feature. And the other way around.
In Angular = SPA xor SSR
In any case, let me make clear that there are good reasons to do SSR with Angular Universal, especially if you are building a customer-facing App on the Internet and you are worried about SEO, Webshare and first-page load performance. But you need to be aware of the architectural decision you are making.
Working with Angular Universal
Let’s see visually how client-side and server-side rendering works.
As explained, with client-side rendering, all the data processing and rendering happens at the browser using XHR.
In contrary, what we seek with pure server-side rendering is that all the data requests and its associated processing are computed at the server. Only the plain view (HTML, CSS and less critical JavaScript) are transferred over the wire, so the browser just displays portions of data in a process we call UI Rehydration.
Notice this is not a tutorial on how to write up an Angular Universal App. There are plenty of good tutorials out there which can help you with this matter. However, let’s go quickly through the main steps on how to get this done using this framework.
Create your project scaffold and Angular Universal configuration using Angular CLI.
Make sure you have an entry for your server-side App on .angular-cli.json file.
- You’ll have to implement your Web server using Node.js and include Angular Universal rendering logic.
You’ll now have to design and build the logic of your App. This is, all your components structure but also all your SPA routing logic using Angular routing. This is an example:
Finally, you’ll have to implement Angular Transfer State pattern. This is the keystone to make Angular Universal do all the server-side rendering stuff, relieving the browser of making calls to fetch and process API data.
In my example I have included this logic in the ngOnInit() event of every component. At the time you are writing this, you are not coding for the server or the client, although you need to include the Transfer State logic because this code will be executed on both sides (yes, it will). So all what the piece of code above does is getting and setting values to a global variable (accesible by the Transfer State object) with data fetched from an API using the http get object. Since the code will be executed on the server first, API data is fetched by the server and stored in a variable for later consumption at the browser side.
Demystifying Angular Universal
To see this in action, we just need to run the App and look at browser and server consoles.
When the App is first launched, we can see that Transfer State is updating the global variable on the server. In my example, it is getting some Post data from a dummy API:
If we look at the browser console, we see that all what it did was retrieving this variable from the Transfer State (UI Rehydration).
This can be confirmed by looking at the network tab in the browser’s developer tools. Here, we can see that there are no calls to the API server, just regular GET calls to download static resources (some of them served from the cache as stated by the 304 response)
This is very good news, it works!
So let’s keep trying. Now, let’s keep navigating through my sample App. In this case, let’s try to display son User dummy data, by clicking on the “Users” link. And here is where things start to get weird.
If we look at the server console, nothing happens. It is as if the event was not captured by the server. As if the navigation routes I have set in the code (see above) have bypassed the server logic, or even worse, never reached the server! The server console is not processing this request.
However, if I look at the browser console, I see how this event and request have been processed at the client side.
This definitely not what we want! This means that the key was updated on the browser because the Transfer State didn’t happen. This also means that the API call was done at the browser. Let’s confirm this by looking at the network tab in the browser’s developer tool:
That’s it. It is not working. We can see the call to the “Users” API is happening at the browser side. Somehow, our App has instantaneously become an SPA again, as all the data fetching, processing and rendering is done at the browser, and all the server-side processing is gone. It is just a plain Angular app making use of all the SPA features.
Conclusion
This behaviour does not qualify Angular Universal to be a framework for building Universal Apps. It looks like it is designed in such a way that SSR capabilities are only leveraged on first load, but after that, events are not captured on the server and UI rehydration does not happen, turning your App into an SPA again. In other words, client- and server-side processing is exclusive after first load.
I wanted to confirm this by looking at Angular’s official guide, and (not) to my surprise, the very last words of the very last statement clear things up a lot.
This guide showed you how to take an existing Angular application and make it into a Universal app that does server-side rendering. It also explained some of the key reasons for doing so.
- Facilitate web crawlers (SEO)
- Support low-bandwidth or low-power devices
- Fast first page load
Angular Universal can greatly improve the perceived startup performance of your app. The slower the network, the more advantageous it becomes to have Universal display the first page to the user.
However, I can’t be more disappointed. I know there are other frameworks out there such as Apollo that, in combination with Angular Universal, can turn your application in a real Universal App, but I somehow expected more about Angular and its native capabilities as a powerful framework to build SPA and SSR applications.
Top comments (2)
Fantastic article! You have really done a nice job breaking down the SSR and Universal/Isomorphic game. Like you, I have many years of experience in this space. Having done universal enterprise applications in Angular, React, and Electrode, I have seen many approaches and implementations, Recently I spent some time coalescing the best parts into Paragons.
Not only is it a fully Universal React enterprise level SPA with SEO support and more but, is also a great learning and demonstration tool.
Really good paper. It'll surely help me when designing the next app.
Great job !
Some comments may only be visible to logged-in visitors. Sign in to view all comments.