Serverless Angular Universal with AWS Lambda

Conor O'Dwyer
The Startup
Published in
6 min readFeb 21, 2021

--

In this tutorial I will demonstrate how to implement server-side rendering (SSR) with Angular Universal and host your application on AWS Lambda and CloudFront. This tutorial assumes you have already developed an Angular single page application (SPA) for which you want to implement SSR. If you don’t already have an Angular application, refer to Angular Tour of Heroes to first build your application.

1. Advantages of SSR

Angular applications execute in the user’s browser and render pages in response to user interaction. This can be clearly seen by inspecting the element of your Angular application. You will see script tags containing JavaScript instead of static content.

Server-side rendering will render your application in advance and generate static pages that get bootstrapped on the client. There are a number of reasons for implementing server-side rendering for your Angular application.

  1. Search Engine Optimization (SEO)
  2. Improved performance on low-powered devices
  3. Quicker initial loading of the first application page
  4. Implementing Google AdSense

2. Implementing Angular Universal

To add Angular Universal to your application, run the following CLI command from your project directory.

> ng add @nguniversal/express-engine

After running this command, you will notice the following files have been created.

  1. src/main.server.ts: exports the AppServerModule, which is the entry point for the Angular Universal application.
  2. src/app/app.server.module.ts: wraps the Angular application along with the ServerModule into a single AppServerModule, which is the entry point for the Angular Universal application.
  3. src/tsconfig.server.json: this file tells the Angular compiler where to find the AppServerModule entry point.
  4. webpack.server.config.js: this specifies the server-side webpack configuration for bundling the Express server (server.ts and import files) into JavaScript code. The is used in conjunction with the updates to package.json to include the “compile:server” script.
  5. server.ts: this is the Express server. It calls ngExpressEngine, which is a wrapper around renderModuleFactory. This is the core functionality that makes Angular Universal work, as it takes the index.html file and generates static HTML pages based on the Angular routes specified for the application.

The following files will also have been modified.

  1. package.json: updated to include new dependencies and new scripts such as the “compile:server” script already mentioned, and the scripts for building and running the Angular Universal application, “build:ssr” and “server:ssr”.
  2. angular.json: the build outputPath is updated from dist/{application_name} to dist/browser and a new architect script is added for “server”.
  3. src/main.ts: updated so that the browser version of the application won’t start bootstrapping until the server-side rendered pages have been loaded.
  4. src/app/app.module.ts: update to replace BrowserModule with the method BrowserModule.withServerTransition({ appId: ‘serverApp’ }), which tells the browser version of the application that the server version will transition to the client version.

3. Run your Angular Universal application

To start your Universal application, run the following commands

> npm run build:ssr
> npm run serve:ssr

The server-side rendered application will then be running on http://localhost:4000. This is the default port that the Universal application will run, however, it can be changed by updating the PORT const value in server.ts.

If you inspect the element on your Universal application in the browser, you will now see HTML that corresponds to the content of your application, instead of script tags which will execute JavaScript in the browser. You should also notice that the performance (time to load your application page) is much quicker.

4. Configure AWS CLI

To deploy your Angular Universal application to AWS Lambda, you will first need an AWS account. If you do not, you can sign up for an account here. You will also need to install AWS CLI if you have not already done so.

Log in to the AWS console and create an IAM programmatic user with the privileges required for creating API Gateways and Lambda functions. With this user created, and AWS CLI installed, run the following command and enter the IAM credentials when prompted.

> aws configure

5. Deploy to AWS Lambda

AWS Lambda is a serverless compute engine, so to deploy your Angular Universal application on AWS Lambda, you will need to implement the Serverless Framework. To configure your application to run as a serverless application on AWS, run the following commands.

> npm install serverless
> npm install aws-serverless-express
> npm install serverless-apigw-binary

The server.ts file should then be updated to remove the app.listen() function call. This is the function that made your application available at http://localhost:4000. This will no longer be needed when deployed on AWS Lambda so should be removed.

The lambda.js file then needs to be created with the following contents. This takes our compiled server.ts function and wraps it in aws-serverless-express.

A serverless.yml file also needs to be created. This provides the instructions to the Serverless Framework for the AWS configuration that is required for deploying the serverless application. Since API Gateway will be the trigger/entry point for our application on AWS Lambda, the serverless-apigw-binary plugin is specified along with the API Gateway configuration. The serverless.yml should be updated for your desired AWS region and the specific packages to exclude/include.

You are now ready to deploy your serverless Angular Universal application. Run the npm run build:ssr command again to rebuild your application, followed by the serverless deploy command which will deploy your application to API Gateway and Lambda. The deployment process may take quite some time.

Once your application is deployed, you will see the following output.

...
endpoints:
ANY - https://{specific_id}.execute-api.eu-west-1.amazonaws.com/production/{proxy+}
ANY - https://{specific_id}.execute-api.eu-west-1.amazonaws.com/production
functions:
...

Navigate to the second URL listed (non-proxy) and you will be able to view your application. This URL however, is not very useful for users to navigate to your application.

6. Add CloudFront

CloudFront has a number of advantages when sitting in front of your application.

  1. Use your own domain name to access application.
  2. Better performance since content will be served at Edge locations closest to the user.

I will only look at #1 in detail and list the steps for setting up a CloudFront distribution.

In the AWS console, navigate to CloudFront and click Create Distribution. In Origin Settings, paste the API Gateway URL (without the /production extension) in the Origin Domain Name field, and enter /production for the Origin Path. Set the Minimum Origin SSL Protocol to at least TLSv1.1 (v1.2 would be better) and set the Origin Protocol Policy to HTTPS Only.

In the Default Cache Behavior Settings section, the only setting that needs to be changed is setting the Viewer Protocol Policy to Redirect HTTP to HTTPS.

In the Distribution Settings section, the Alternate Domain Names (CNAMEs) field should be updated to include your Domain name entries. The SSL Certificate should also be updated to point to the SSL certificate for your domain. You can import the certificate if you already have it or create one in ACM if not.

Click Create Distribution. It will take a couple of minutes for your distribution to be created. When your distribution is created, AWS will automatically create a Domain Name for you, in the format *.cloudfront.net. The last step is to add a CNAME entry with this Domain Name in your Domain Manager, e.g. GoDaddy. The entry should look something like this.

If you navigate to your domain you should now see that your Angular application is running.

--

--