User Guide: Hosting Single Page Application using AWS CloudFront and AWS S3

Mar 3, 2023

This is the second part of the mini-series covering the best way to host Single Page Applications (SPAs) or Static Websites using Static Site Generation (SSG) on AWS. The first part talked about AWS Amplify, this time we’ll focus on deploying a SPA using AWS CloudFront and AWS S3.

In this guide
Prerequisites | Using AWS Console Guide | Using Terraform as Infrastructure as Code | Learn More About Using AWS CloudFront and AWS S3

Prerequisites

  1. To follow the steps in this guide, you will need to have the SPA or SSG build artifacts ready;
  2. Additionally, refer to the Hosting Single Page Application (using AWS Amplify) guide for instructions on how to get started with the create-react-app framework.

Using AWS Console Guide

Step 01: Create an S3 Bucket

  • Navigate to the AWS S3 Console;
  • Click on “Create new Bucket”;
    • Specify the following:
      • Bucket name
      • Region
      • Block public access; this is currently the default;
      • Encryption; AWS SSE S3 encryption is currently the default;

User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 1

  • After successfully creating the bucket, it will be empty.

Step 02: Upload the build artifacts

  • Click on “Upload”;
  • Grab the contents of the “build” folder and drag and drop them into the console;
User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 3

  • You should see that the content is ready to be uploaded to the console (as shown below);
User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 5
  • Press the “Upload” button;
  • Navigate back to the AWS S3 bucket and check the result.
User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 7

Step 03: Create an AWS CloudFront distribution:

  • Navigate to the AWS CloudFront Console page;
  • Click the Create distribution button;
    • Specify the Origin Domain;
      • Select the appropriate Amazon S3 bucket from the list (in our case, static-hosting-example-demo.s3.eu-central-1.amazonaws.com);
    • Leave the Origin Path field blank;
    • Leave the Name field as is (for us, it will be static-hosting-example-demo.s3.eu-central-1.amazonaws.com);
    • Select the Origin access control settings option from the Origin access radio buttons;
      • Select the cdn CDN Policy from the Origin access control tab and click the Create control settings button.
User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 9

Step 04: Specify the Default cache behavior

  • Keep the Path pattern as the default value (*);
  • Enable Compress objects automatically by selecting yes;
  • Under the Viewer protocol policy, select Redirect HTTP to HTTPS;
  • Allow only GET and HEAD HTTP methods by selecting them from Allowed HTTP methods;
  • Under the Cache key and origin requests setting, select Cache policy and origin request policy;
    • Choose Caching Optimized as the cache policy;

User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 11

  • Specify the default root object as index.html;
  • Leave all of the other settings as they are;
  • Click on the Create Distribution button.

Step 05: Update the bucket policy of the AWS S3 Bucket

  • Copy the bucket policy provided to you. After creating a distribution, a pop-up will appear in the console with the policy information;
User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 13

  • Click on the provided link;
  • Paste the content into the designated area;
  • Click on the Update policy button;
User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 15

Step 06: Additionally, it is recommended to set up the Error pages

  • For SPAs that use client-side routing, it is recommended to set up 404 and 403 errors and point them to index.html;
  • For SSGs, generate different error pages such as 403.html and 404.html, and configure the errors accordingly;
User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 17

Step 07: Wait for the distribution to update

  • Updating different aspects of the AWS CloudFront distribution can take up to minutes.

Step 08: Visit the domain of the distribution

  • You should see that the SPA/SSG application is running.

Using Terraform as Infrastructure as Code

Step 01: Prerequisites

Step 02: Create a terraform directory at the root of the project

  • Navigate to the terraform directory you just created.
mkdir terraform
cd terraform

Step 03: Create a Terraform provider file named provider.tf and set up the basics for the resources

# provider.tf
terraform { 
	required_providers { 
		aws = { 
			source = "hashicorp/aws" 
			version = "~> 4.16"
		} 
	} 
	required_version = ">= 1.2.0" 
} 

provider "aws" { 
	region = "us-east-1" 
}

Step 04: Create the AWS resources in the main.tf file

# main.tf
data "aws_caller_identity" "current" {}
# some locals specified will be reused later
locals {
  static_hosting_bucket = "static-hosting-bucket-1999" # change it for yourself
  s3_origin_id          = "CDN-Demo"
  account_id            = data.aws_caller_identity.current.account_id
}

# static hosting S3 Bucket
resource "aws_s3_bucket" "static_hosting" {
  bucket = local.static_hosting_bucket
}

# S3 Encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "generic_encryption" {
  bucket = aws_s3_bucket.static_hosting.bucket
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# S3 Public Access Block
resource "aws_s3_bucket_public_access_block" "block_public_access" {
  bucket = aws_s3_bucket.static_hosting.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# S3 Bucket Versioning
resource "aws_s3_bucket_versioning" "bucket_versioning" {
  bucket = aws_s3_bucket.static_hosting.id
  versioning_configuration {
    status = "Enabled"
  }
}

# CloudFront Origin Access Control
resource "aws_cloudfront_origin_access_control" "cdn_access_control" {
  name                              = "cdn-1"
  description                       = "CDN Policy"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

# CloudFront distribution
resource "aws_cloudfront_distribution" "cdn" {
  origin {
    domain_name              = aws_s3_bucket.static_hosting.bucket_domain_name
    origin_access_control_id = aws_cloudfront_origin_access_control.cdn_access_control.id
    origin_id                = local.s3_origin_id
  }

  enabled             = true
  is_ipv6_enabled     = true
  comment             = "GLOBAL CDN 1"
  default_root_object = "index.html"

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = local.s3_origin_id

    cache_policy_id        = "658327ea-f89d-4fab-a63d-7e88639e58f6" # THIS IS A BUILT IN
    viewer_protocol_policy = "redirect-to-https"
  }

  price_class = "PriceClass_All"

  viewer_certificate {
    cloudfront_default_certificate = true
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
      locations        = []
    }
  }

  custom_error_response {
    error_code    = 404
    response_code = 200
    response_page_path = "/index.html"
  }

  custom_error_response {
    error_code    = 403
    response_code = 200
    response_page_path = "/index.html"
  }
}

# 
resource "aws_s3_bucket_policy" "cloudfront_access" {
  bucket = aws_s3_bucket.static_hosting.id
  policy = data.aws_iam_policy_document.cdn_policy.json
}

data "aws_iam_policy_document" "cdn_policy" {
  statement {
    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }

    actions = [
      "s3:GetObject",
    ]

    resources = [
      aws_s3_bucket.static_hosting.arn,
      "${aws_s3_bucket.static_hosting.arn}/*",
    ]

    condition {
      test     = "ForAnyValue:StringEquals"
      variable = "AWS:SourceArn"
      values   = ["arn:aws:cloudfront::${local.account_id}:distribution/${aws_cloudfront_distribution.cdn.id}"]
    }
  }
}

# Output the domain name to use it later
output "cf_domain_name" {
  value       = aws_cloudfront_distribution.cdn.domain_name
  description = "Domain name"
}

Step 05: Deploy the resources

# Initialize the terraform
terraform init
# Plan
terraform plan
# Deploy
terraform apply

Step 06: Wait for the result

  • If the deployment is successful, you should see the output cf_domain_name written to the console, as shown below:
User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 19

Step 07: Copy the build files to S3

# navigate to the build folder
cd ../build

# use the AWS CLI to copy the files
aws s3 cp . s3://static-hosting-bucket-199 --recursive
# You should rename the bucket ofc

Step 08: Check the result

  • Navigate to the domain name of the AWS CloudFront distribution that has been created. The terraform apply command should output the domain name.

Learn More About Using AWS CloudFront and AWS S3 With Cloudvisor 

Creating a static hosting solution for SPA and SSG applications with AWS CloudFront and S3 is an easy and cost-effective option. This mini-series provides a step-by-step guide on how to use the AWS Console to create a static hosting solution. Additionally, the second part of the article shows how to use Terraform as Infrastructure as Code (IaC) to deploy these resources.

Struggling to find the right AWS resources for your workloads and optimal configurations for these resources? Switch to a Proactive AWS DevOps Support and Monitoring Service with Cloudvisor! When you subscribe to our Proactive Support Service, our engineers will recommend the latest AWS best practices for you to implement and administer monthly reviews to ensure your infrastructure complies with these best practices.

User Guide: Hosting Single Page Application Using Aws Cloudfront And Aws S3 21

Written by Sándor Bakos

AWS Cloud Engineer/Architect at Cloudvisor

Switch to a Proactive AWS DevOps Support and Monitoring Service with Cloudvisor!
Our engineers will recommend the latest AWS best practices to implement and administer monthly reviews to ensure your infrastructure complies with these best practices. Learn More!

Cloudvisor: We Live and Breathe AWS​

Cloudvisor is an advanced-tier AWS partner operating in Europe, USA, and beyond. Our diverse, globally distributed team includes highly experienced Amazon Web Services professionals.

More Blog Posts

Recent AWS Guides

AWS Webinars

AWS Whitepapers

Our Services

AWS Resell

As an advanced AWS Partner, Cloudvisor gives your business the opportunity to access industry-leading cloud services at unbeatable prices instantly.

AWS Cost Optimization Review

Get an AWS Cost Optimization Review to ensure that you are only using the AWS services the right way and only when you actually need them.

AWS Well-Architected Framework Review

Make sure your AWS Infrastructure complies with AWS Best Practices with an AWS Well-Architected Framework Review. 

Monitoring Service

Switch from reactive DevOps support to a dedicated, proactive support service that helps reduce costs while boosting performance.

Migration to AWS

We have significant experience in AWS migration and understand the complexity of adopting a new cloud services solution. Our team can handle the whole process for you, from start to finish.

Data Engineering Services

Ready to Unlock the power of data for your business? We help companies unlock data’s power for their businesses. Start your journey today!

AWS Security

Security is at the heart of everything we do. We focus on AWS Edge security services, including WAF and Shield, as well as the Amazon CloudFront service, one of the most secure CDNs on the market today.

AWS Marketplace

Our team can help you navigate through all the products and services available on the AWS marketplace and build a suite of tools tailored to your unique business needs.

Subscribe to Our Monthly Newsletter

Stay in the loop with AWS through our monthly newsletter. Unlock its full power with insider tips and updates. 💡




    Other Blog Posts