Pre-rendering in Angular with Angular Universal: Should you consider it?
We developers often talk about Client Side Rendering vs Server Side Rendering, but there's another option called Pre-rendering. If utilized properly pre-rendering can boost your website performance and SEO rankings especially if you cannot afford the cost of server-side rendering your application.
Here at Halodoc, we use a hybrid of server-side rendering and pre-rendering. We have pre-rendered certain pages such as the home page, security bounty page, a couple of articles pages that have high traffic, etc. Most of these pages serve static content and are publicly accessible. All of the other pages are server-side rendered.
Before diving into pre-rendering, let us take a step back and understand what is CSR ( Client Side Rendering ) and SSR ( Server Side Rendering ) to get a more clear picture.
In CSR, the rendering is done in the browser itself, generally, the browser sends a request to the CDN which delivers the base HTML page containing javascript, and CSS links. The browser on receiving the page, will download the required resources, fetch the required data and then bootstrap the application. In SSR, as you may have already guessed, the rendering takes place on a dedicated server. As soon as the browser sends a request, the server fetches the required data, populates the base HTML page, and then sends it to the browser. This is how all the web apps were built a few years ago until CSR was introduced. You can read more about CSR and SSR here.
What is Pre-rendering?
Pre-rendering is a rendering technique in which pages can be pre-rendered at build time and sent to the browser as and when it is requested.
Pre-rendering can be classified into three types based on the implementation. Using Angular Universal, or services such as prerender.io or pre-rendering using <link> tag.
The name Universal is self-explanatory. As we all know, Angular was initially developed to run only on the browser. Angular Universal allows us to run the same code on the server as well. One thing to keep in mind when working with Angular Universal is that, some objects such as window, document, and global object are only available in the browser. Also, storages such as local storage and session storage are only available on the browser. We can make use of isPlatformBrowser and isPlatformServer from '@angular/common' to prevent a certain piece of code from running on the server and vice versa.
Then, we have Prerender.io which offers a pre-rendering service. Prerender.io enables us to do something called Dynamic Rendering. First, we need to integrate prerender.io with our server or CDN. Now when a request arrives, if the user-agent is a browser, then the base HTML page and javascript files are served but if the user-agent is a crawler, then a request can be sent to the prerender.io renderer to fetch the pre-rendered HTML page if available or else it is rendered and then stored in a cache. Subsequent requests would then be served from the pre-renderer cache. You can read more about Prerender.io here.
The main difference between the two is that in pre-rendering using Angular Universal we serve pre-rendered pages to both browsers and crawlers whereas using prerender.io we just do it for the crawlers. However, Google considers dynamic rendering a workaround and not a long-term solution. They recommend using server-side rendering and static rendering (Pre-rendering).
Pre-rendering can also be done using <link> tag. The syntax would look something like this:
<link rel="prerender" href="https://example.com/home">
This way of pre-rendering is not recommended because of the extreme cost associated with it in terms of memory. In fact, Firefox and Safari do not support it at all. Chrome also does not do a full pre-render, but just loads the resources beforehand. I will not go into depth on this as it is not very relevant or useful for frameworks like Angular but you can read more about it here.
How does Pre-rendering work?
So basically when an Angular application is built we provide a set of routes that need to be pre-rendered. For each of these routes, an index.html file is generated at dist/browser/<routeName> by default, when the application is built. These HTML pages can be stored in an S3 or a server. A CDN can be set up to serve these HTML pages.
Now, whenever a browser sends a request, that particular pre-rendered HTML page can be served using the CDN. Now as the browser displays the pre-rendered HTML page, Angular bundles are downloaded in the background and once the download is complete Angular will take over. Any further rendering has to be done by fetching data from an API server and then bootstrapping it to an HTML page on the client side.
How is Pre-rendering different from SSR?
Some of the key differences that can be highlighted are :
- SSR involves rendering the HTML page only when a browser requests it whereas in pre-rendering we select only a few routes that are mostly static and have high traffic, and generate it at build time.
- As SSR is done at runtime it will take some time to render the HTML page on the fly and depends on factors such as the computing capacity of the server, location of the server, etc. Pre-rendering is very fast when compared to SSR as we directly serve the pre-rendered HTML page on request. These pages can be served using CDN to further improve the performance.
- The cost of SSR is high both in terms of money and time, as it requires the maintenance of a separate server.
Factors to consider before choosing Pre-rendering:
- Performance
This is one of the most important factors when deciding how your application is going to be rendered. This is especially important for mobile and low-powered devices. According to a recent study, 53% of mobile website users abandon a website if it takes longer than 3 seconds to load. Speed is one of the signals based on which google ranks websites. So a faster website will be ranked better.
I just ran a lighthouse test using chrome developer tools in developer mode( we can see a better result in production mode ), to compare performance between a page that is CSR, SSR, and then pre-rendered, here are the results for the desktop version:
As you can see, we can improve the initial page load of a webpage just by pre-rendering it. LCP which is one of the core web vitals improved from 7.8s when client-side rendered to 1.6s when pre-rendered rendered. FCP, TTI, and total blocking time also improved. This performance boosts is especially important if you have decided not to server-side render your application. Let me also show you the performance metrics of a pre-rendered webpage served using CDN in a production environment.
If your website is an internal one, used only by the employees within the company then performance may not be something that is on your priority list. But for public-facing websites performance is vital. So you should definitely consider pre-rendering parts of your website.
2. SEO
I'm assuming you know what SEO is. I'll just explain in brief.
SEO stands for Search Engine Optimisation. It is all about improving the visibility of your webpage to attract more traffic.
You can read more about SEO here.
No matter how fast and interactive a website is, or how good the UI/UX is, ultimately a website has to have a good reach, and its capability to bring new users in, matters. Search engines have crawlers that crawl webpage for content. As I explained earlier a CSR application will not have any content in its body initially, so the crawlers won't be able to crawl the content. This is not entirely true because search engines such as Google, Bing, DuckDuckGo, etc now have the capability to crawl CSR applications as well. Google uses something called a two-wave approach. So in the first wave, only the initial HTML is indexed and then the second wave of indexing is done to render javascript only once the resources are available. And it can take days or weeks for this second wave to be completed.
Until a few years ago, only Googlebot (Google Web Crawler) could render javascript and then index it but now many search engines are capable of this. Having said that, it may not be ideal to entirely rely on crawlers to render javascript and then index the content. Crawlers have to spend a lot more time rendering javascript and thus can lead to partial indexing ( some of the contents of the page handled by javascript may not have been indexed ) affecting the webpage ranking. This is the reason why many companies are inclined towards server-side and pre-rendering. Having pre-rendering or SSR ensures that our application doesn't need a second wave before its SEO scores improve. You can read about the render budget and crawl budget for more information on the SEO budget.
3. Accessibility
One of the questions you should ask yourself before deciding if you want to pre-render your application is if any of my web pages are accessible publicly without the need for authentication. If yes, then you should definitely think about pre-rendering. If not, then it would not make any sense to pre-render because when you pre-render your webpages at build time, you will not have the login details, so anything behind the authentication screen will not be pre-rendered.
4. Nature of Content
If the content on your webpages pages changes frequently based on an API call or based on any event then those pages may not be an ideal candidate for pre-rendering. For those changes to reflect you need to rebuild your application.
5. Dynamic Routes
Pre-rendering routes which are dynamic, are difficult. Consider an example, a page that displays patient details and has a route structure of /patient/{patient_id}. There can be thousands of patients with different ids such as /patient/1, patient/2. Specifying each route does not make sense. This does not mean it is not possible, let me give you another example, at Halodoc we have pre-rendered some article pages which have the highest traffic. The route structure is something like /article/{article_slug}, similar to the previous example but in this use case, it actually makes sense, as pre-rendering the top 10-15 articles which garner the highest traffic can boost SEO and user experience.
Steps to pre-render an Angular application:
Now, you have considered all the factors mentioned above and have decided to go ahead with pre-rendering, but how do you start? Setting up pre-rendering is quite simple now thanks to the @nguniversal/express-engine schematic. Earlier, all the changes had to be done manually by the developer.
Step 1: Run the following command in a terminal inside the project directory:
ng add @nguniversal/express-engine
Step 2: Modify the pre-render configuration in the angular.json file as needed. By default, Angular tries to guess all the routes that can be pre-rendered and then generate index.html for those. We can prevent this by setting the guessRoutes option to false.
Here we can add separate configurations for stage and production as defined under configuration. We can specify the routes which we want to pre-render using the routes options. There are also other ways to specify the routes if the number of routes is more. We can add the routes in a text file and then mention it using the routesFile options (replacing routes options in pre-render builder configuration).
Step 3: Now we have to pre-render all the possible routes using the following command:
npm run prerender
We can also specify routes to pre-render using the routesFile flag in the same command :
npm run prerender --routesFile routesFile.txt
routesFile.txt is the text file containing routes to be pre-rendered.
Step 4: Start the server by running the following command:
npm run serve:ssr
By default, the server runs at port 4000. So now if we navigate to localhost:4000/<routeName> ( ex: localhost:4000/home ) you can view the pre-rendered page.
How do I know if my application is being pre-rendered?
It is actually quite simple to know if an application is pre-rendered. Let me first show you what a client-side rendered page looks like. Just open the website, right-click anywhere and then click on "View Page Source". If the page source looks like the below image (the element is empty except for the one div which is hard-coded in the index.html file ) then it is client-side rendered. This is what a crawler sees when it visits your page, nothing much to crawl upon for a crawler, right? This should give you an idea of why search engines prefer pre-rendered and server-side rendered pages.
A pre-rendered page source looks something like:
As you can see the app-root element contains all the content to be displayed to the user even before javascript kicks in. One point to note here is that the page source of a server-side rendered application would also look the same. But if you scroll down the page source, at the bottom of an Angular application pre-rendered with Angular Universal, you will find:
<!-- This page was prerendered with Angular Universal -->
Conclusion:
So is pre-rendering a one-stop solution for everything? Absolutely Not. Pre-rendering has its own set of pros and cons. It does not make much sense to pre-render your application if your webpage lives behind an authentication screen, your page has content that changes frequently, or you just have a static HTML page that is not a javascript-enabled framework ( such as Angular, React, Vue, etc ), or your endpoint/route is dynamic.
However, I would definitely use pre-rendering in my application wherever possible, because it is simple to set up, easy to maintain, boosts SEO, and improves performance.
Join us
We are always looking out for top engineering talent across all roles for our tech team. If challenging problems that drive big impact enthral you, do reach out to us at careers.india@halodoc.com
About Halodoc
Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek and many more. We recently closed our Series C round and In total have raised around USD 180 million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalised for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.