Server Side Rendering with Angular 10

Aayushi Raval

Oct 09, 2020 | 3 min read

Why Do We Need Server-side Rendering?

Angular applications are client-side applications that execute on the browser - which means they are rendered on the client, not on the server. You can add server-side rendering to your app using Angular Universal.

There are two main reasons to create a server-side version of your application:

  • Performance: Rendering Angular on the server-side improves the performance of your application, particularly on mobile and low-powered devices since the browser will not need extra time to render content which reduces the time for the First-contentful Paint
  • SEO: Server rendering allows search engine crawlers to easily crawl your web app which helps with Search Engine Optimization

What is Angular Universal?

Angular Universal is a technology that takes care of rendering Angular applications on the server. It runs on the server-side and generates pages that are sent to the client browser which allows the application to render more quickly, giving users a chance to view the application layout before it becomes fully interactive.

Getting started with Angular Universal

Step 1 - Add @nguniversal

From your app directory open a terminal and run the following command:

ng add @nguniversal/express-engine --clientProject YourProjectName 

This command will install the required packages and it will add and update all required files to implement server-side rendering in angular application.

CREATE src/main.server.ts
CREATE src/app/app.server.module.ts
CREATE src/tsconfig.server.json
CREATE server.ts

UPDATE package.json
UPDATE angular.json
UPDATE src/main.ts
UPDATE src/app/app.module.ts
UPDATE tsconfig.json

Step 2 - Building and Serving the App

run the following commands:

npm run build:ssr
npm run serve:ssr

This will serve the angular application with server-side rendered pages on http://localhost:4000 address

npm run build:ssr : This command compiles and create dist folder and in that folder there were  2 folders /browser and /server, normal build and server build.

dist
   -browser
   -server
Folder Structure

npm run serve:ssr : This command serve /server folder only, but when any request is made from browser, it serve template and other static files from /browser folder.

Things to remember:

Since a Universal application runs on the server and not in a browser, there are a few things you need to watch out for in your application code:

  • Check your use of browser-specific objects, such as window, document, or location. These don’t exist on the server.
  • By importing the functions isPlatformBrowser and isPlatformServerfrom @angular/common, injecting the PLATFORM_ID token into your component, and running the imported functions to see whether you’re on the server or the browser.

Example:

import { OnInit, PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformServer } from '@angular/common';

export class SomeClass implements OnInit {
    constructor(@Inject(PLATFORM_ID) private platformId: Object) { }
    ngOnInit() {
        if (isPlatformServer(this.platformId)) {
            // do server side stuff
        }
    }
}

"window is not defined" And "Document is not defined"

One of the most common issues when using Angular Universal is the lack of browser global variables in the server environment.

Solution for "window is not defined":

Create service file (Example:  global-object.service.ts)

For Example:

import { Injectable, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Injectable()
export class GlobalObjectService {
  constructor(@Inject(DOCUMENT) private doc: Document) {}

  getWindow(): Window | null {
    return this.doc.defaultView;
  }

  getLocation(): Location {
    return this.doc.location;
  }

  createElement(tag: string): HTMLElement {
    return this.doc.createElement(tag);
  }
}

After creating service file use this file wherever window object is used

import { GlobalObjectService } from '@shared/services';
import { isPlatformBrowser } from '@angular/common';


export class AppComponent implements OnInit {
  constructor(
      windowRef: GlobalObjectService,
      @Inject(PLATFORM_ID) private platformId: object,
  ) {
      this.windowRef = windowRef.getWindow();
  }
 
 ngOnInit() {
      if (isPlatformBrowser(this.platformId)) {
        this.windowRef.scrollTo(0, 0);
      }
  }
 }

This code is solve "Window is not defined" error

Solution for "Document is not defined":

The above solution is also use for "document is not defined" error but if you still facing this error you can also use this code in server.ts file

const domino = require('domino');
const fs = require('fs');
const path = require('path');
const templateA = fs
  .readFileSync(path.join('dist/NinsUserFrontend/browser', 'index.html'))
  .toString();
const win = domino.createWindow(templateA);
win.Object = Object;
win.Math = Math;

global['window'] = win;
global['document'] = win.document;
global['branch'] = null;
global['object'] = win.object;

Conclusion:

We've seen that Server-side rendering works by rendering a part of your client-side app on the server instead of the client.

This can help increase performance and allow all search engine crawlers and social media networks to easily crawl your application for SEO purposes.

We also used the @nguniversal/express-engine to quickly create a Universal version of our Angular application and prepare our app for server-side rendering.

Reference:

https://angular.io/guide/universal
https://github.com/angular/universal/blob/master/docs/gotchas.md