Summary
The pattern provided in this post is a step-by-step approach to hosting an SPA that’s written in React on AWS S3 and AWS CloudFront. The SPA in this pattern uses a REST API that’s configured in AWS API Gateway and exposed through an AWS CloudFront distribution to simplify cross-origin resource sharing (CORS) management.
We're going to be using this sample repository provided by AWS.
Architeture
AWS Services
- AWS API Gateway helps you create, publish, maintain, monitor, and secure REST, HTTP, and WebSocket APIs at any scale.
- AWS CloudFormation helps you set up AWS resources, provision them quickly and consistently, and manage them throughout their lifecycle across AWS accounts and Regions.
- AWS CloudFront speeds up distribution of your web content by delivering it through a worldwide network of data centers, which lowers latency and improves performance.
- AWS CloudTrail helps you audit the governance, compliance, and operational risk of your AWS account.
- AWS CloudWatch helps you monitor the metrics of your AWS resources and the applications you run on AWS in real time.
- AWS IAM helps you securely manage access to your AWS resources by controlling who is authenticated and authorized to use them.
- AWS Route 53 is a highly available and scalable DNS web service.
- Amazon S3 is a cloud-based object storage service that helps you store, protect, and retrieve any amount of data.
CloudFormation (IaC)
In the sample repository, we will primarily focus on the information available on the react-cors-spa-stack.yaml file. In this YAML file, we have all the instructions for AWS CloudFormation to provision the other AWS resources needed in this sample.
For this post, we won't dive into details of the resources used to provision the REST API of the project.
AWS S3 Bucket
-
PublicAccessBlockConfiguration
actively blocks all public access - Bucket logs are enabled and being stored in the
LoggingBucket
-
BucketName
sets the name of the bucket (that's important information if you are having trouble finding the bucket in the AWS Console)
S3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Sub 'react-cors-spa-${SimpleAPI}'
PublicAccessBlockConfiguration:
BlockPublicAcls : true
BlockPublicPolicy : true
IgnorePublicAcls : true
RestrictPublicBuckets : true
LoggingConfiguration:
DestinationBucketName: !Ref LoggingBucket
LogFilePrefix: s3-access-logs
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'AES256'
AWS S3 BucketPolicy
- Allows
s3:GetObject*
Action -
CFDistributionSPA
is the only entity allowed to perform the Actions3:GetObject*
BucketPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
PolicyDocument:
Id: MyPolicy
Version: 2012-10-17
Statement:
- Sid: PolicyForCloudFrontPrivateContent
Effect: Allow
Resource: !Sub ${S3Bucket.Arn}/*
Principal:
Service: cloudfront.amazonaws.com
Condition:
StringEquals:
AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CFDistributionSPA}
Action: 's3:GetObject*'
Bucket: !Ref S3Bucket
CloudFront
- Sets the origin as the SPA S3 Bucket, using the
CloudFrontOriginAccessControl
as the OAC method between CloudFront and AWS S3 - Property
DefaultRootObject
receivesindex.html
. That's the file from the AWS S3 Bucket that CloudFront is going to consider as being the root object.
CFDistributionSPA:
#checkov:skip=CKV_AWS_68: "For demo purposes and to reduce cost, no WAF is configured"
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
Origins:
- DomainName: !GetAtt S3Bucket.RegionalDomainName
Id: myS3Origin
S3OriginConfig:
OriginAccessIdentity: ""
OriginAccessControlId: !GetAtt CloudFrontOriginAccessControl.Id
Enabled: 'true'
DefaultRootObject: index.html
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- OPTIONS
TargetOriginId: myS3Origin
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # CachingOptimized
OriginRequestPolicyId: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf # CORS-S3Origin
ResponseHeadersPolicyId: eaab4381-ed33-4a86-88ca-d9558dc6cd63 # CORS-with-preflight-and-SecurityHeadersPolicy
ViewerProtocolPolicy: redirect-to-https
CustomErrorResponses:
- ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
- ErrorCode: 501
ResponseCode: 501
ErrorCachingMinTTL: 0
PriceClass: PriceClass_All
Logging:
Bucket: !GetAtt LoggingBucket.RegionalDomainName
Prefix: 'cf-spa-access-logs'
ViewerCertificate:
CloudFrontDefaultCertificate: true
MinimumProtocolVersion: 'TLSv1.2_2021'
CloudFront OriginAccessControl
- Describes the request signatures between CloudFront and AWS S3 Bucket (OAC)
CloudFrontOriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
DependsOn:
- S3Bucket
Properties:
OriginAccessControlConfig:
Description: Default Origin Access Control
Name: !Ref AWS::StackName
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
Deploy
-
First, run the following command in order to CloudFormation provision the application resources
aws cloudformation deploy \ --stack-name react-cors-spa-demo \ --template-file react-cors-spa-stack.yaml \ --region us-east-1 \ --no-fail-on-empty-changeset
-
Now, we're going to retrieve the values exposed by our CloudFormation template that are listed in the
Outputs
section
Outputs: BucketName: Value: !Sub "react-cors-spa-${SimpleAPI}" SPADomain: Value: !GetAtt CFDistributionSPA.DomainName APIDomain: Value: !GetAtt CFDistributionAPI.DomainName
Run the command
aws cloudformation describe-stacks \ --stack-name react-cors-spa-demo \ --region us-east-1 \ --query "Stacks[0].Outputs"
Save theBucketName
for a future step Build your React application. In the sample, we can achieve this by running
yarn build
and locating theout
folder.-
Now we simply need to upload the content from the previous step, using the
BucketName
from step 2, to the AWS S3 Bucket
aws s3 sync ./out "s3://$BUCKET_NAME"
If you're using Next.js like the AWS Sample, you must also include the_next_
folder in your AWS S3 Bucket
aws s3 sync ./_next "s3://$BUCKET_NAME"
Conclusion
Now you know how to host your React SPA application in a fast, secure, and cost-effective stack inside the AWS Cloud.
If you found this content helpful or have any questions about it, feel free to leave a comment below!
Top comments (0)