(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.
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.
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 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.
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.
- You’ll have to implement your Web server using Node.js and include Angular Universal rendering logic.
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.
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.
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.