Angular server-side rendering in Node with Express Universal Engine

The Universal project started a long time ago, while Angular was still in beta. After one year and half of waiting, it is now possible to activate server-side rendering on a real Angular app !

But that’s not just a config property to activate for now, so I’ll explain all the steps. Thanks to Philippe Martin and his article Creating an Angular Universal app with the Angular CLI, reproduced in the CLI official wiki. I started from here and many points are already explained, but some points are missing to apply it on a real app, so let’s start.

A French version of this post is available here.

CLI updates

Note that CLI 1.6 now has native Universal support. You just have to do :

ng g universal chooseaspecificnameforuniversal

Since CLI 6 :

ng g universal --clientProject yourappname

It does all the front-end config for Universal which was explained here, so it’s no longer relevant.

You’ll still need to manage the server-side, this post can still help you about this.

Note I don’t have time to answer all the support requests in comments. About frequent questions :

Universal bundle

First, you need Angular ≥ 4.3. If you didn’t already upgrade, do it now, it’s really easy.

You also need Angular CLI ≥ 1.3.0 and platform-server :

npm install @angular/cli @angular/platform-server

Be aware that direct update of the CLI often causes errors. If you have errors while building, try to remove node_modules and package-lock.json and npm install again.

The next steps are just copy/paste, and the CLI may automate them soon.

First, you’ll have to add a new app config in your .angular-cli.json to generate the server bundle, in addition to the browser bundle.

Then you’ll need a specific AppServerModule, next to the normal AppModule, which will override all APIs with ServerModule, to adapt them to server usage :

Then a new main-server.ts entry point, next to the normal main.ts :

And a new tsconfig.server.json, next to the normal :

Finally, just a small change to your normal AppModule :

Why this change ? Because if the content is already pre-rendered by the server, now the browser doesn’t need anymore to create all the app by itself : HTML and CSS are already there, it just needs to apply bindings and other Angular stuff to be dynamic.

Now you can update your npm scripts in package.json :

npm run build

Server-side rendering in Node

Except the output hashing option, the packaging steps were already explained in the wiki. But explanations about the server are quite light.

Contrary to the wiki, we won’t use platform-server directly, but the official Universal express engine :

npm install express @nguniversal/express-engine

Then you can create your server.js file :

And run :

node server.js

Ta da ! To be sure the server is doing its work : deactivate JavaScript in your browser, so you know if something is really pre-rendered.

But odds are it won’t really work. Some important points need to be managed in a real app.

An example repo is available here.

Pitfall n°1 : being Universal compatible

Why the server-side rendering project is called Universal ? Because Angular code can be used in any context, not just in the browser. So your JavaScript/TypeScript code will also work on the server.

So you have to respect very strictly this principle of being platform-agnostic. It means to never access directly browser-specific APIs. It includes :

  • window, document… and other browser objects and their methods (like setTimeout)
  • all DOM APIs
  • all other browser-specific APIs like localStorage, IndexedDB…

The two first points are not a problem : you just have to use Angular templating (or Angular rendering API, for advanced usage). But what if you really need localStorage or another browser-specific API ?

The minimal solution : a try/catch to silence the error when the code will be used outside the browser. A better one : Angular tells you what is the current context :

Pitfall n°2 : Http requests

You’ll find that all content coming from Http requests won’t be pre-rendered: it’s because Universal needs absolute URLs.

As your development and production server won’t have the same URL, it’s quite painful to manage it on your own.

My solution to automate this : using the new HttpClient interceptor feature of Angular 4.3, combined with the Express engine.

The interceptor catches all requests when in server context to prepend the full URL, and looks like this (just copy/paste) :

Then provide it in your AppServerModule :

Now you can use Express engine to pass the full URL to Angular, just update your server.js :

Note : you need to use the new HttpClient from Angular 4.3. It won't work on requests done with the previous Http API.

I opened an issue to propose a PR including this directly in Express engine, feel free to vote.

Pitfall n°3 : 3rd party librairies

Last problem : all the packages you use in your app must be Universal compatible, and avoid the first pitfall.

All stable Angular 4+ packages are compatible with Universal.

For third party librairies, you’ll have to test by yourself.

By the same author