Hosting a static Single Page Application on AWS using the CDK

Maarten Thoelen
HatchSoftware
Published in
8 min readJan 9, 2022

A complete guide on hosting a SPA on AWS including automatic CI/CD

Two years ago I wrote a blog post on how you could use the AWS CDK to build scheduled Lambda functions. At that time the CDK was only 1 year old and had just become generally available for .NET and Java, next to the existing versions for Typescript and Python.

In the past 2 years the AWS CDK gained more and more traction and it has established itself in the IaC space. About a month ago, during re:Invent 2021, AWS announced the general availability of the CDK v2, including a preview for Go.

For me, the biggest value of the CDK is its concise method for representing infrastructure that could be easily abstracted into constructs (building blocks) together with the ability to use it with the programming language of my choice. The success of the construct programming model shows in the fact that besides the classic CDK, there are now also projects that apply the CDK concepts to define Kubernetes applications (CDK8s) and even Terraform based infrastructure (CDKtf by HashiCorp). Useful constructs for all three CDK versions can be found in the recently launched Construct Hub, which is an online catalog for construct libraries created by the community, AWS and cloud technology providers.

In the meantime, we also adopted the CDK for many use cases at HATCH. One of them is hosting Single Page Applications we create for our clients.

In the next steps I will explain how to host an existing SPA by adding some CDK magic.

Prerequisites

The start

We start off with an existing SPA. The one I am using is a very minimalistic Angular sample app that only has one page, the home page.

Let’s run the app to see what it looks like.

npm start

If we want to host this application, we first need to create a distributable package. In angular you do this by running the command below.

npm run build

After running this command, you’ll see the distributable files in the ‘/dist‘ folder on the root level.

Adding in the CDK

In order to add the CDK into this application we need a folder in which we will put all infrastructure code. We’ll create it in the root of the application.

mkdir infrastructure

Next up is initialising this folder with the CDK. As my Angular application is written in Typescript, I’m also using Typescript for my CDK code.

cd infrastructure/
cdk init app --language typescript

Doing this results in the following structure:

Hosting

Now we can get started writing the actual code. First we’ll focus on the hosting part.

Building the stack

We will need a CDK stack that defines all infrastructure needed to host our application as a static website. Therefore we will delete the ‘infrastructure-stack.ts’ file and create a new ‘static-site.stack.ts’ file.

Within this stack we’re going to create following infrastructure:

  • An S3 bucket to store the static files of the application (content of the ‘dist’ folder)
  • A CloudFront distribution to serve the application files over HTTPS
  • An Origin Access Identity to restrict the S3 file access to the CloudFront distribution only
  • An SSL certificate for the domain we are going to use
  • Route53 TXT records to validate the ownership of the domain in order to request the SSL certificate
  • Route53 A records to point the domain to the CloudFront distribution
  • A one time S3 deployment to put the static files in the S3 bucket

After adding all necessary code, our file looks like this:

As you can see, we are creating CloudFormation output values for the S3 bucket name and the CloudFront distribution ID. We’ll need to use these outputs in the CI/CD stack that we’ll build later on.

Another thing worth mentioning is the SSL certificate being requested in the ‘us-east-1’ region. This is done because this is the only region currently supported by CloudFront.

Deploying it

We are about to deploy this infrastructure, but before we can do this we will need to add the code that creates our stack to the ‘infrastructure.ts’ file.

We are creating the stack in a specific account and region. For the domain name I’m using the subdomain ‘static-cdk.hatch.be’.

To start the actual deployment we execute the command below inside the ‘infrastructure’ folder:

cdk deploy cdk-web-static-stack

After a few seconds, the CDK will provide you with an overview of all changes that will be done in order to deploy the infrastructure. It will also ask you to confirm the deployment.

When you confirm the deployment, you’ll see all the resources being created one by one.

If you want, you can also follow up in more detail in the AWS CloudFormation console.

When the deployment is done, you’ll see that all infrastructure is present.

The S3 bucket:

The CloudFront distribution:

The DNS records in Route53:

The SSL certificate

When we now browse to our domain, we can see the content of our application being served over HTTPS.

As a final step, we push this code to a GitHub repository.

CI/CD

The initial version of the application is now being hosted, but we also want it to be updated automatically in case there are future changes in the application code. We do this by creating a CI/CD pipeline.

Building the stack

We create a new file for the CDK stack that defines all infrastructure needed for our CI/CD pipeline. Let’s call this file ‘ci-cd.stack.ts’. Within this stack we’re going to create following infrastructure:

  • A CodePipeline with a ‘Source’ and ‘Build’ stage
  • A GitHub source action that will download the code from our GitHub repository
  • A CodeBuild project that will
    - create the application distributable package
    - push it to the S3 bucket
    - create a CloudFront invalidation
  • A SNS topic + subscription to get notifications in case we have build failures

After adding all the code, our file looks like this:

The source action needs the correct permissions to download the source code from the GitHub repository. We will provide these permissions by specifying a newly created personal access token and storing this one in the AWS SecretsManager.

aws secretsmanager create-secret --name /static-cdk/cicd/github_token --secret-string '{"github-token":"<<YOUR GITHUB TOKEN>>"}'

The role used by the CodeBuild project also requires permissions to sync the distributable files with the S3 bucket and invalidate the CloudFront distribution. These permissions are granted to the role by adding them explicitly to the principal policy of the Role.

Deploying it

Next up is deploying the CI/CD stack, so let’s create an instance of the stack in the ‘infrastructure.ts’ file.

In order to deploy the new distributable files to the correct bucket and invalidate the correct distribution, we import the S3 bucket name and CloudFront distribution ID we exported earlier inside our static site stack. We do this by using the same output ID’s. The imported values are passed in to the CI/CD stack.

We also provide information on the GitHub repository where the source code resides (repo, repoOwner, repoBranch) and the mail address where we want to receive notifications in case of pipeline failures.

Now, let’s deploy.

cdk deploy cdk-web-cicd-stack

Again, you’ll see all resources being created in the CloudFormation console.

During the creation of the stack, we get a mail to confirm the subscription to the CI/CD pipeline failure notifications.

Once the stack is created, a first run of the CI/CD pipeline is automatically triggered. However we won’t see any differences with the already deployed application because we did not make any changes to the application source code yet.

Now, let’s see what happens if we do so by changing the content of our home page.

When we commit and push this change to the repository, a new run of our CI/CD pipeline will be triggered automatically.

When this run completes, we can see our content changes in the hosted application.

Clean up

If you no longer need your hosted application and CI/CD pipeline or you need to completely remove it for any other reason, you can delete all created resources with the following command.

cdk destroy --all

If you only want to remove one of the stacks you can respectively use one of the commands below.

cdk destroy cdk-web-static-stackcdk destroy cdk-web-cicd-stack

Final code

The final code of this project can be found on Github. If you want to use it, don’t forget to specify the correct AWS account, region, repository information and any other parameter placeholders.

Thanks for reading!

Maarten

--

--