Setting up a complete CI/CD pipeline for React applications doesn’t have to be complex. In this guide, I’ll walk you through creating an automated deployment pipeline that takes your code from commit to production using GitHub Actions, AWS S3, and CloudFront.
Table of Contents
What We’re Building
By the end of this tutorial, you’ll have:
- Automated testing and building on every pull request
- Automatic deployment to AWS S3 + CloudFront on merge to main
- Cache invalidation to ensure users see updates immediately
- Preview deployments for pull requests
Prerequisites
- A React application ready to deploy
- AWS account with S3 and CloudFront access
- GitHub repository for your project
- Basic familiarity with GitHub Actions
Step 1: AWS Infrastructure Setup
Create S3 Bucket
First, create an S3 bucket to host your static website:
aws s3 mb s3://your-app-production --region us-east-1
aws s3 website s3://your-app-production --index-document index.html --error-document error.html
Set Up CloudFront Distribution
Create a CloudFront distribution for global CDN delivery:
{
"CallerReference": "your-app-production",
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "S3Origin",
"DomainName": "your-app-production.s3.amazonaws.com",
"S3OriginConfig": {
"OriginAccessIdentity": ""
}
}
]
},
"DefaultCacheBehavior": {
"TargetOriginId": "S3Origin",
"ViewerProtocolPolicy": "redirect-to-https"
}
}
Configure IAM User
Create an IAM user with these permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-app-production",
"arn:aws:s3:::your-app-production/*"
]
},
{
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation"
],
"Resource": "*"
}
]
}
Step 2: GitHub Actions Workflow
Create .github/workflows/deploy.yml
:
name: Deploy React App
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --coverage --watchAll=false
- name: Run linting
run: npm run lint
- name: Upload coverage reports
uses: codecov/codecov-action@v3
build:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-files
path: build/
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-files
path: build/
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Sync to S3
run: |
aws s3 sync build/ s3://${{ secrets.S3_BUCKET }} --delete
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
Step 3: Advanced Features
Cache-Busting Strategy
Add cache-busting to your build process by updating package.json
:
{
"scripts": {
"build": "react-scripts build && npm run optimize",
"optimize": "node scripts/optimize-build.js"
}
}
Create scripts/optimize-build.js
:
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
function addCacheHeaders() {
const buildPath = path.join(__dirname, '../build');
const staticPath = path.join(buildPath, 'static');
// Add hash to static files
const files = fs.readdirSync(staticPath, { recursive: true });
files.forEach(file => {
if (file.endsWith('.js') || file.endsWith('.css')) {
const filePath = path.join(staticPath, file);
const content = fs.readFileSync(filePath);
const hash = crypto.createHash('md5').update(content).digest('hex').substring(0, 8);
const newName = file.replace(/\.([^.]+)$/, `.${hash}.$1`);
fs.renameSync(filePath, path.join(staticPath, newName));
}
});
}
addCacheHeaders();
Preview Deployments
Add preview deployments for pull requests:
preview:
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'pull_request'
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-files
path: build/
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to preview
run: |
PREVIEW_PATH="previews/pr-${{ github.event.number }}"
aws s3 sync build/ s3://${{ secrets.S3_BUCKET }}/${PREVIEW_PATH}
echo "Preview URL: https://your-domain.com/${PREVIEW_PATH}" >> $GITHUB_STEP_SUMMARY
Step 4: GitHub Secrets Configuration
Add these secrets to your GitHub repository:
-
AWS_ACCESS_KEY_ID
– Your IAM user’s access key -
AWS_SECRET_ACCESS_KEY
– Your IAM user’s secret key -
S3_BUCKET
– Your S3 bucket name -
CLOUDFRONT_DISTRIBUTION_ID
– Your CloudFront distribution ID
Step 5: Monitoring and Optimization
Add Build Notifications
- name: Slack notification
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment ${{ job.status }} for commit ${{ github.sha }}'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
Performance Monitoring
Add bundle size checking:
- name: Check bundle size
run: |
npm run build
npx bundlesize
With bundlesize
configuration in package.json
:
{
"bundlesize": [
{
"path": "./build/static/js/*.js",
"maxSize": "300 kB"
},
{
"path": "./build/static/css/*.css",
"maxSize": "50 kB"
}
]
}
Troubleshooting Common Issues
Build Failures
- Check Node.js version compatibility
- Verify all environment variables are set
- Review test failures in the Actions tab
Deployment Issues
- Confirm S3 bucket policies allow public read access
- Verify CloudFront distribution is pointing to correct origin
- Check IAM permissions for S3 and CloudFront access
Cache Issues
- Ensure CloudFront invalidation is running
- Verify cache-control headers on S3 objects
- Consider using versioned URLs for assets
Conclusion
You now have a production-ready CI/CD pipeline that automatically tests, builds, and deploys your React applications with zero downtime and instant cache invalidation. This automated workflow will save hours of manual deployment work while ensuring consistent, reliable releases that scale with your development team.
The pipeline includes advanced features such as preview deployments, comprehensive testing, and performance monitoring — exactly what you need for professional React application deployments.
Need help implementing CI/CD pipelines or AWS infrastructure for your project? I specialize in DevOps consulting and can set up production-ready deployment automation in days, not weeks. Check out my portfolio and services or send me a message to discuss your project.
This is part 1 of my “DevOps for Startups” series. Part 2 covers building a scalable AWS infrastructure with Terraform soon.