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)