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
- Prerequisites
- Step 1: AWS Infrastructure Setup
- Step 2: GitHub Actions Workflow
- Step 3: Advanced Features
- Step 4: GitHub Secrets Configuration
- Step 5: Monitoring and Optimization
- Troubleshooting Common Issues
- Conclusion
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.
Top comments (0)