How To Deploy Angular Universal Using AWS (Enterprise Grade)

Using AWS Lambda, AWS ECR x Docker, API Gateway x CloudFront, CloudFormation, GitHub Actions, AWS CLI (on a high level) [Includes Frontend Architecture Too]

Charlie Greenman
Razroo
6 min readApr 6, 2023

--

I know. That’s the Angular Universal Logo. The first time I’m seeing it too!
An Angular Universal Partnership Between GMind And Razroo

To purchase this code and have it automatically be generated to your codebase, go to https://razroo.com/finder/razroo/angular-universal-with-aws-17.0.0.

Backend Architecture At A High Level

GitHub Actions x ECR + Docker Container To Contain Lambda

In your frontend repository, you will create a Dockerfile that will build your front end into a Lambda.

This Docker container will be deployed to ECR using our Docker container. In our GitHub Actions, we will use the AWS CLI + our GitHub secret, to gain access to AWS. We will build our frontend code via Github actions and deploy it to ECR.

CloudFormation x Lambda x API Gateway + Cloudfront

To deploy and manage resources in the Amazon Web Services (AWS) cloud, AWS CloudFormation will be used. We will do this by writing a CloudFormation script in YAML. The purpose of this script will be to create a CloudFront (CDN) URL layered over an API Gateway URL. This will ensure that there is a content delivery network for Server Side x AWS Lambda-served content.

In addition, the script will create a serverless function using AWS’s native AWS::Serverless::Function. This function will use the Dockerfile located in the AWS Elastic Container Registry (ECR) instance. By using CloudFormation to define and deploy infrastructure as code, AWS resources will be easily managed and maintained.

Lambda x Compression

The maximum size a request object can have for AWS Lambda is 1MB. Your application’s main.js file may exceed 1MB. To bypass this, you will need to set up compression inside of your server.ts file, so request objects are smaller than 1MB.

Binary Mime Types

Being that we are using serverless to serve our files, we want to make sure that it’s able to retain the binary value of our files. We set up behind scenes a tracker to determine the mime type of a file. In addition, a list of file types that should be treated as binary.

CloudWatch x Lambda Handler

To track server-side errors in development and production, we will set up strategic console logs inside of our lambda handler. This will allow us to see real-time errors in our CloudWatch dashboard in case things are not working as expected.

Local Dockerfile For Rapid Development

We will create a separate Dockerfile for our local environment. This will allow us to build the Dockerfile locally if need be, and iterate at a fast rate. In addition, debug locally without breaking development, staging, or production environments.

Use API Key For Public Queries

It is quite possible your team already had this setup before. If it did not, you will need to create a new API key for every environment. Inside your schema.graphql these new keys will be attached on a query-per-query basis. It will make it so these queries can be used publically for SEO purposes.

Frontend Architecture At A High Level

Window Service + Local Storage Service

Window and local storage are DOM-specific global variables. Any window or local storage-related functions are not needed and cannot be used for Angular Universal. We will create a window service and a local storage service that are server-side friendly. All local storage + window instances will be swapped with this service.

RxJS

RxJS timer-related functions such as `timer` and `setInterval` will not work within an Angular Universal setting. In addition, any subscribers that are not completed on the initial page view will cause the page to completely error out. In general, it helps to re-factor erroring out RxJS blocks to `.pipe(take(1), catchError(() => EMPTY)).subscribe(` when possible. This causes errors to silently fail.

isPlatform Is Your Friend And Savior

There will be various instances, either 3rd party plugins or nuanced use cases that will not work with server-side rendering. Wrapping that block of code inside of an isPlatform(isBrowser) will allow you to move past those roadblocks.

Authentication x Server Module x Client Module

By definition, an SEO crawlable site is public. For this reason, all authentication libraries have DOM-specific code and will fail when being called by Angular Universal.

The most efficient approach we have found towards solving this one is to create a new shared module and auth module for authentication. The shared module will be used by both the client module and the server module. The auth module will then be re-imported by the client module only.

For the server module to work as expected, you will create faux classes for authentication-related libraries.

Server.ts, Landing Page + Cookies

Being that we are now using Angular Universal, guards will not work as expected, as server-side rendering will render first before guard. For the landing page, we passed a cookie into the server.ts file itself to determine if a user visited a page yet. Using this approach will redirect to the landing page immediately, giving users a near-native experience.

Development Compiler Hack

It is worth noting that running SSR dev mode will take quite some time. What we did within our team is added advice with README.md to run non-SSR ng serve when possible. Before pushing, run SSR in development mode just to be on the safe side.

Generate this blog article using Razroo

SEO Architecture At A High Level

Robots + Sitemap for Development + Production

We will create a different `robots.txt` and `sitemap.xml` for development and production. This will allow our development environment to be crawled and for production to be crawled.

SEO Service x Ngrx/Store Effects

We created an SEO service that used the Meta + Title Angular-native packages to update metadata for SEO purposes. These services were then called inside of our Ngrx/effects to make it as explicit as possible as to when this metadata is updated.

In addition, by putting SEO service inside of our Ngrx Effects, it made sure that metadata was updated before being consumed by the web crawler.

In Conclusion

This is the architecture on a high level of how to deploy to AWS. It is a very complicated task and is a Tour De Force of both backend + frontend skills due to it requiring knowledge of literally all the things. At Razroo, we went through this entire process from the ground up with GMind helping us throughout the entire process. We understand how complicated the entire process is and wanted to write some documentation to help.

To purchase this code and have it automatically be generated to your codebase, go to https://razroo.com/finder/razroo/angular-universal-with-aws-17.0.0.

Note: We did not have the chance to write complete documentation yet. In the meantime, please reach out to sales@gmind.tech to hire GMind for consulting. Alternatively, reach out to Charlie Greenman on Twitter

--

--