Server-side Rendering (SSR) with Angular Universal

Share on facebook
Share on google
Share on twitter
Share on linkedin

As the name suggests, Single-page App (SPA) is a single HTML document that can be initially served to the client. Any new views required in the application can be created by using JavaScript solely on the client. Besides this, the request-response cycle still happens, but only to RESTful APIs to get static resources or images.

In addition to this, there are a lot of advantages to writing applications in this manner. However, there are some things that you might lose. For instance, web crawlers to index the app and slower the loading performance. So, Server-Side Rendering (SSR) is used to bridge the gap.

What Is Server-Side Rendering (SSR)

In server-side rendering, the server will return a static web page that is compiled with dynamic data. This data is ready to be displayed on the browser and also comes with the client-side scripts that are needed to make the page dynamic. To fetch the dynamic data, the developer has to write server-side scripts with the use of server-side languages. This was the web page rendering process in the old days with the use of technologies like PHP, Perl, and CGI. But recently it has gained traction with technologies, like Angular and Express. Server-side rendering is SEO-friendly and is the best solution for low-power devices.

What Is Client-Side Rendering (CSR)?

In client-side rendering, the partial web page is returned by the server without dynamic data, but it offers the client-side scripts that are required to fetch the data on-demand that are asynchronous.

Here, no one but the client is responsible for data fetching while loading a new page or when there is user interaction. In CSR, there are many desynchronized calls to the server. Besides, client-side rendering is not SEO-friendly, as the content in this process is always dynamic.

Why Do We Need Server-side Rendering?

As we know,  Angular apps are client-side apps that are executed on the browser. This means that Angular apps are rendered on the client and not on the server. But with the help of Angular Universe, we can add server-side rendering to any Angular application. But the main question is, why do you need to do that? There are two main reasons that will explain why an Angular developer needs to create a server-side version of the application:

  • Performance: By rendering the Angular app on the server-side, the developer can improve the performance of the app, particularly on the low-powered and mobile devices since the browser of these devices will not require extra time to render content. This can help reduce the time for the First-contentful Paint.
  • SEO: Server rendering enables the search engine to easily crawl the web application which eventually helps with SEO.

JavaScript apps can be rendered and indexed on Google, but not all search engines are as advanced. Social media networks like Facebook only consider server-rendered HTML. To see this, share your client-side application on social media.

Let’s show some examples:

  1. Sharing some URLs on Facebook which do not have the SSR facility.

They create the same content in the info-box for two different URLs of the same site. It means it returns some static content for all the pages of the site.

  1. Sharing some URLs on Facebook which have the SSR facility.

They create different content in the info-box for two different URLs of the same site. It means it returns dynamic content based on the content of the page.

Therefore, the developer needs to make sure that every social network and search engine can recognize the content of the Angular web app. For this, they need to create universal apps or implement the old server-side rendering.

Now, let us create a standard Angular app using some Angular best practices that is by default development for client-side rendering. After that, we will use the new Angular schematic to configure the app as server-side rendering.

Create a Standard Angular App

Step 1

Check whether you have the latest Angular CLI which is 9 or greater.

ng --version

If your CLI version is not as required, upgrade it.

npm i -g @angular/cli

Step 2

Create a new Angular app.

 ng new angular-SSR

 A new Angular project will be created and packages will be installed by the angular CLI.

Step 3

Run the app and observe the web page’s content.

After the successful installation of all the packages, we can run the app.

cd angular-SSR

 npm start

You can now check out the message that the development server is running at http://localhost:4200/.

Observe that the HTML page’s source is served by the app. Besides this, you aren’t able to see the static HTML for the page’s content, as most of the content has been dynamically loaded with the help of client-side scripts.

As you can see here, the app created with CLI is set up to have Client-side rendering. And with the help of Angular Universal, we can configure the app as server-side rendering with ease.

Configure for Server-Side rendering (SSR)

Step 4

From @nguniversal, we are going to the latest Angular Universal diagrammatic. It helps in adding an express server to the project.

ng add @nguniversal/express-engine

This is what you will observe:

All needed files have been created. You just need to observe the following as it has been added to the package.json. 

"@angular/platform-server": "~11.2.4",

"@nguniversal/express-engine": "^11.2.1",

"express": "^4.15.2",

Also, observe the newly added shortcuts/scripts in package.json

"dev:ssr": "ng run angular-SSR:serve-ssr",

"serve:ssr": "node dist/angular-SSR/server/main.js",

"build:ssr": "ng build --prod && ng run angular-SSR:server:production",

"prerender": "ng run angular-SSR:prerender"

Step 5

Run the app and check the web page content.

npm run dev:ssr

We can see that the server has delivered the entire static HTML page with all elements in pure server-side rendering. The requirement of the client-side script is only when there is user interaction, it is not responsible for fetching the required data. However, when we talk about the Angular Universal, there are hybrid approaches like universal templates and more. 

Other considerations:

We know that the Universal app does not run on the browser but on the server. For this, there are a few things that a developer needs to watch out for in the code – 

  • Have a look at the usage of browser-specific objects like documents, windows, or location. These aren’t available on the server. Try to use an injectable Angular abstraction like Location or Document. If you really need them then wrap their usage in a conditional statement. It can then only be utilized by Angular on the browser. We can do this by importing the two functions isPlatformServer and isPlatformBrowser from @angular/common. Then injecting the PLATFORM_ID token into the component and running the functions that are imported to check whether you’re on the browser or a server. (To check a quick solution see –  Additional tips to optimize server.ts)
  • If you make use of ElementRef to get a reference to an HTML element then don’t try to use the nativeElement in order to manipulate attributes on the element. We can try to inject Renderer2 and then use one of the methods.
  • If browser event handling doesn’t work then your app won’t respond to any click events or browser events when it is running on the server. In such cases, any link that is generated from a routerLink will help in navigation.
  • Use setTimeout less or only where necessary.
  • Create all URLs for server requests. The reason behind it is that the requests for data from relative URLs are going to fail when it runs from the server, even if the server has the capacity to handle relative URLs.  (Check a quick solution for Handling requests for relative path and only allowing GET request on the server-side)

Avoiding Duplicate HTTP Calls in Angular Universal

When a developer is working with Angular Universal, it can present a set of unique challenges. The HTTP calls are duplicated on both the client and server apps.

You can solve this problem in many different ways, but it clearly depends on your specific scenario.

If your application is using Angular’s HttpClient to make HTTP calls then the solution is a straightforward one. In such cases, you can use TransferHttpCacheModule of Angular Universal on your ServerTransferStateModule and app.module on your app.server.module.

But, if you want to utilize the TransferHttpCacheModule, you must first install it. It can be installed as part of the top-level application module.

After that import ServerTransferStateModule in the Server module.

When this is done, the HTTP calls are generated with the use of HttpClient and this will not result in duplicate calls, especially, when the Universal app is loaded on the browser.

Handle the relative path requests and on the server-side, only allow the GET requests.

There are chances that you might run into problems especially when you try to use the relative paths in requests on the server-side. Here, when you want to solve the problem, one of the solutions is to provide the full URL to your app that is on the server, and then you can write an interceptor that can help in retrieving this value and evaluate the request URL. When you are using the ngExpressEngine, as you can see in the example in this guide, you are halfway there. Here, we can consider that this is the case, but it is not important to offer the same functionality.

Also, we need to add a condition for the GET method. The reason behind it is that we only cache GET requests.

Let’s start this by creating an HttpInterceptor.

Now, offer the interceptor for the server AppModule in the providers.

Now, this interceptor will be fired and it will replace the request URL on every HTTP request made on the server. Here the absolute URL is offered in the Express Request object.

Additional Tips to Optimize the server.ts:

  1. As we know that the Universal apps run on the server more than on the browser, there are some of the major things you need to watch out for in the app code. You can have a look at the browser-specific objects like document, window, or location and if any of these are missing, so we recommend you to use domino for Server-side DOM abstraction. Hereby Domino we mean a Server-side DOM implementation that is based on Mozilla’s dom.js. Here is what you need to do-

●    Install domino npm for server-side dom abstraction

●   Then, configure the “server.ts”

  1. Gzip compressing

It is a process that can greatly decrease the size of the response body and also increase the speed of a web application. For gzip compression, you can use the compression middleware in your Express application.

  1. X-Frame-Options 

It enables the content publishers to restrict the attackers from getting to their content. To prevent the content, the publishers can use the DENY option. It is one of the most secure options to prevent the use of the current page in a frame.

There are chances that the Hackers can exploit the familiar vulnerabilities in Express/Node. This can especially happen if they see that your website is powered by Express. For example, by default X-Powered-By: Express is sent in each and every HTTP request that comes from Express. This process won’t offer security but it can help in some ways.

The last point is that if you want to stop the server-side rendering for some specific URLs, then you can send the index.html file directly as a response.

Conclusion

As seen in this blog, Angular Universal is a pre-render builder that enables the developers to render the application on the server-side. It pre-renders the application while there is a first hit on the website from the user. Angular universal  and server-side rendering is beneficial for the accessibility, performance, and search engine optimization of the web-pages. To sum it up, Server-side rendering with Angular Universal can boost the app’s performance and make it SEO-friendly. 

Author Bio:

Vikash K is working at TatvaSoft.com. He is a passionate writer and you can find more of his bylines all over the internet.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments