Angular Universal
Adding Angular Universal Server Side Rendering To An Existing ANgular 7,8,9 project and deploying to AWS Elastic Beanstalk
(valid on 06.10.2019)
The first page to refer is official documentation (https://angular.io/guide/universal) that claims that adding Universal (SSR – Server Side Rendering) to an existing Angular project is very easy:
Run ng add @nguniversal/express-engine --clientProject your-project-name
Run npm run build:ssr && npm run serve:ssr to see Universal in action.
Several things that I've added and modified, problems which faced and resolved:
-
"Window is not defined"
As on server side you don’t have the browser itself you cannot use objects like window, document, location, etc. If you use it you see in node console something like
"window is not defined"
. To avoid this you have several options.
The first one is to create an abstraction that substitutes window object while your app runs on server. You can use this helpful guide from Brian F Love - It solves the problem of undefined object but still you cannot, for example, get the ‘window.innerWidth’ in pixels, because actually you don’t have any real window with any real width.
The second options, suggested here by Kravchenko Igor, is to use a library like domino.js. This library's goal is to provide a DOM in Node. Pay attention, that it doesn't support all the features of DOM. So you still can sometimes get an error in style like "Feature is not supported" instead of "window is not defined". -
3d party libraries using window
Some of libraries directly use document object which is not available in Universal. You can easily solve this problem by using PLATFORM_ID token. You can see an example here.
-
Using cookies
For me the perfect match for using cookies with Universal was the Cookie Service from ngx-universal library. It's easy to intergate and use both on client and server app runs.
-
Duplicate calls to API on client side
With SSR you receive the whole page content ready from the server (check in the network – you will receive the document) and you don’t need to make calls to API to bring data again when Universal app is substituted with a client-side one’s. But it happens. Official angular docs suggest to use TransferStateModule. Adding BrowserTransferStateModule to app.module.ts and ServerTransferStateModule to app.server-module.ts is supposed to solve the problem. You can also write your own logic for customising this service - use Angular’s TrasferState class. This service from the same library - ngx-universal - is a great example.
-
Provide the first page responsively to screen sizee
As we cannot detect screen width on server side we can use user-agent string in request headers and define isMobile variable by user-agent and not by screen width.
-
How to restrict serving specific routes via Universal
In server.ts before general get request to regular routes you can add:
app.get('/user-zone/**', (req, res) => { res.sendFile(join(DIST_FOLDER, 'index.html')); });
This will prevent serving the page via universal for any route starting with "user-zone".
Deploying Angular Universal to AWS Elastic Beanstalk
Here is one the good guides. Generally, you don't need to change anything neither in Angular project nor in AWS EB environment settings (all values are default). The only thing was in AWS EB configuration that you have to enter to Configuration->Software and set “Node command” to ‘node dist/server.js’ (solves error 502 Gateway). Or any other path to you main server.js script.
How to deploy:
Run the following npm script (provided built-in with universal module): "build:ssr" – it uses environment.prod.ts, so change it if needed or create a new environment. Angular documentation: https://angular.io/guide/build. Archive as .zip the “dist” folder (important to zip not the contet of the folder but the “dist” folder itself). Upload to AWS EB that .zip fileA note on Angular 9
After updating to Angular 9 you will see that now you have a new server.ts file and the old one is now backuped. Please go through the new file carefully to check if nothing is broken.
If you have any questions please feel free to email me!