How to build an Angular App with Server-Side Rendering

There are a few reasons you may want to use Server-Side Rendering with your Angular application.

  1. SSR helps with Search Engine Optimization. Search engines can parse the page since it is rendered on the server.
  2. Socia media platforms like Facebook and Twitter can show a preview of the site when shared.
  3. Once a webpage is rendered on the server, you can cache it and serve it much faster.

To implement server side rendering in your Angular application, you can use the Angular Universal package.

Angular Universal

Create new Angular project:

ng new project-name

Inside that project, download the following packages and add Angular Universal:

cd project-name
npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader@3.5.0 express
ng generate universal project-name

This creates and updates the following files:

create src/app/app.server.module.ts
create src/main.server.ts
create src/tsconfig.server.json
update package.json
update .angular-cli.json
update src/main.ts
update src/app/app.module.ts
update .gitignore

Modules: You now have two separate root modules: app.server.module.tsand app.module.ts. The server module imports ServerModule from the @angular/platform-server package. The browser module calls theBrowerModule.withServerTransition() method which tells Angular we are using server side rendering and the view has to be swapped once the full framework is loaded.

Entry Point: You also have two entry points for you application: src/main.ts and src/main.server.ts. The latter is the entry point for the server, and simply exports our server module.

Config files: To tell the Angular compiler that we have two entry modules, the tsconfig.server.json file is created. The tsconfig.app.json compiles the browser application.

Angular CLI: In the angular-cli.json file, a second profile is added for the server bundle.

Bootstrapping: Your main.ts file is updated with the following function:

document.addEventListener('DOMContentLoaded', () => {
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
});

This is to make sure the Angular application is bootstrapped after the DOM is loaded. The application bootstrap logic is placed inside the DOMContentLoaded event.

Node Server

Next, you will need to create a server in the root directory of the application. This file will use the Javascript file produced by running npm run build:ssr using the server application configured in the .angular-cli.json file. It is then applied to the index.html page. Create a server.ts file in the root directory of your project and add the following code:

The server.ts file needs a webpack configuration to generate the Javascript file to run on the server. Create a webpack.server.config.js file in the root directory of your application and add the following code:

To your package.json file, add the following commands to your scripts array:

"build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
"serve:ssr": "node dist/server.js",
"build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false",
"webpack:server": "webpack --config webpack.server.config.js --progress --colors"

First run npm run build:ssr and when that is completed, run npm run serve:ssr. Your application should be served on localhost:4201.

Transfer State

When we use Angular Universal, the API that delivers the content is hit twice. First when the server is rendering the page and second, when the application is bootstrapped. This causes latency issues and a bad user experience as the screen usually flickers when this happens. See the diagram below to see how how it works:

designed by Upstate Interactive, LLC

We can use the TransferState service to send information from the server to the client, which avoids making duplicate API calls. See how this works below:

designed by Upstate Interactive, LLC

Let’s use the TransferState service in our application. In app.module.ts, import the BrowserTransferStateModule:

import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
imports: [
BrowserModule.withServerTransition({appId: 'my-app'}),
BrowserTransferStateModule,
...
]

In app.server.module.ts, import the ServerTransferStateModule:

import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
imports: [
AppModule,
ServerModule,
ServerTransferStateModule,
...
]

We can use the makeStateKey function to create a key, to store data in the state (which will be passed down to the browser). You will use this.state.get to get data from the state and this.state.set to set data in the state. When an API call is made, you will store the data returned in the state using the key you created with makeStateKey.

In the file you are consuming your API, import the TransferState and makeStateKey modules.

import { TransferState, makeStateKey } from '@angular/platform-browser';

Inject the TransferState service into your constructor function:

constructor(
private state: TransferState,
...
) {}

Create keys to store your data:

const KEY_NAME = makeStateKey('variable_name');

Within your function in which you are calling to an API, get your data from the state using this.state.get. If the entry is not found, make your http call. When you get your data back from the http call, store it in the state using this.state.set.

functionName() {
let variable_name = this.state.get(KEY_NAME, null as any);
if (variable_name) {
return Observable.of(variable_name);
}
return this.http.get('url')
...
this.state.set(KEY_NAME, variable_name as any);
return variable_name;
}

Now your client will not make an HTTP call when the data comes back from the rendering server, because it is stored in the state.


If you run your app after following these steps, your application will not make any HTTP calls from the client. You can confirm this by looking at the network tab in the developer tools of your browser. You should not see any XHR requests, since the request is being made in the server and sent to the browser as HTML.

Lastly, not every application warrants server-side rendering. Rather than explaining it myself, I would recommend reading this article for a thorough overview of when SSR is right for your project. Also, a lot of my understanding on this subject matter came from following along with the following tutorial: http://www.dotnetcurry.com/angularjs/1388/server-side-rendering-angular-nodejs. I highly recommend following along and building the application yourself.

To take this a step further, check out Part II: Setting up Angular Universal with ngrx.

I hope this was helpful!


☞ We help B2B organizations turn great ideas into software. Interested in learning more?

☞ You may enjoy the following articles, too.