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.
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 :
- there is a specific module to load if you have lazy-loaded routes ;
- there is now in Angular ≥ 5 a module to avoid HTTP requests to be done twice.
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 tsconfig.app.json :
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 :
But odds are it won’t really work. Some important points need to be managed in a real app.
Pitfall n°1 : being Universal compatible
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
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
- Angular schematics extension for VS Code : GUI for Angular CLI commands
- @ngx-pwa/local-storage: 1st Angular library for local storage
- Other Angular libraries: @ngx-pwa/offline & @ngx-pwa/ngsw-schema
- Other popular Angular posts on Medium
- Follow me on Twitter
- Angular onsite trainings (based in Paris, so the website is in French, but my English bio is here and I’m open to travel)