This project demonstrates how to deploy a React single-page application (SPA) to AWS S3 and serve it securely through CloudFront, with all infrastructure defined as code using Terraform and automated deployment via GitHub Actions.
-
Infrastructure as Code (Terraform)
Provisions:
- Private S3 bucket configured for static website hosting
- CloudFront distribution
- Origin Access Control (OAC) → secure bucket access (no public bucket policy)
- Proper SPA routing support (
index.htmlfallback for all paths)
Typical workflow:
terraform init terraform plan -out=tfplan terraform apply tfplan
aws configure
aws s3 sync build/ "s3://${S3_BUCKET}" --delete
aws cloudfront create-invalidation --distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" --paths "/*"
These steps are fully automated in the GitHub Actions workflow:
- GitHub Actions CI/CD Workflow Triggers on changes to the build/ folder (or manual dispatch)
- Uploads files directly to S3
- Invalidates CloudFront cache
- Important architectural decision:
- ❌ No npm run build inside CI
- ❌ No lint / test steps in deployment pipeline
- ✅ Deploys pre-built artifacts only
- Reason: CI=true makes ESLint treat warnings as errors → avoids unnecessary CI failures.
↓
commit build/ folder
↓
GitHub Actions (on push / manual)
↓
AWS S3 (private bucket)
↓
CloudFront (HTTPS + caching + global CDN)
- React (CRA)
- Terraform ≥ 1.5
- AWS S3 + CloudFront + Origin Access Control
- GitHub Actions
- AWS CLI
- Infrastructure and application deployment concerns are cleanly separated
- S3 bucket stays private (secured via OAC — no public bucket policy)
- CloudFront provides HTTPS termination, caching, and global distribution
- Deployment is fast, repeatable, and secure
After successful terraform apply:
https://yourcloudforntdomain.cloudfront.net
# or whatever domain alias / custom domain you configured
Use the cloudfront_url or cloudfront_domain_name output value
.png)