DEV Community

Cover image for Deploy a React SPA using AWS S3 and CloudFront
Arthur Colman
Arthur Colman

Posted on

Deploy a React SPA using AWS S3 and CloudFront

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

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' 
Enter fullscreen mode Exit fullscreen mode

AWS S3 BucketPolicy

  • Allows s3:GetObject* Action
  • CFDistributionSPA is the only entity allowed to perform the Action s3: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 
Enter fullscreen mode Exit fullscreen mode

CloudFront

  • Sets the origin as the SPA S3 Bucket, using the CloudFrontOriginAccessControl as the OAC method between CloudFront and AWS S3
  • Property DefaultRootObject receives index.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' 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Deploy

  1. 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 
  2. 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 the BucketName for a future step

  3. Build your React application. In the sample, we can achieve this by running yarn build and locating the out folder.

  4. 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)