How to Deploy a React App to S3 + CloudFront
Hosting a React SPA on AWS is two services: S3 holds the built files, CloudFront caches them globally. Here's the deploy - sync to S3, front it with CloudFront, and handle SPA routing + cache busting.
The architecture
- S3 stores the static build output (
index.html+assets/). - CloudFront is the CDN in front of S3 - it caches your files at edge locations worldwide, adds HTTPS, and keeps the bucket private (no public bucket needed).
Build and sync to S3
npm run build # produces dist/ (Vite) or build/ (CRA)
aws s3 sync dist/ s3://my-app-frontend --delete
--delete removes files in the bucket that are no longer in the build, so old hashed
assets don't pile up.
Put CloudFront in front
Create a distribution with the S3 bucket as the origin. The two SPA-specific settings:
- Default root object =
index.html. - SPA routing: a client-side router means
/dashboardhas no file in S3 -> S3 returns 403/404. Configure CloudFront to returnindex.html(200) for 403/404 so the React router can handle the path. (Custom error responses: 403 -> /index.html -> 200.)
Invalidate the cache on every deploy
CloudFront caches aggressively, so a new deploy won't show until you invalidate:
aws cloudfront create-invalidation \
--distribution-id <ID> --paths "/*"
Hashed asset filenames (Vite/CRA emit app.[hash].js) can cache forever; you mainly need
to invalidate index.html.
The details that matter
- Keep the bucket private - use CloudFront Origin Access Control (OAC) so only CloudFront can read S3, not the public internet.
- HTTPS for free - CloudFront terminates TLS; point your domain at it via Route 53.
- Long cache for assets, short for index.html - hashed assets are immutable; the HTML entry point should revalidate so new deploys appear.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Syncing a build to S3 with --delete
- Configuring CloudFront for SPA routing (403/404 -> index.html)
- Invalidating the cache on deploy
FAQ
How do I deploy a React app to AWS?
Build it, aws s3 sync the output to an S3 bucket, and serve it through a CloudFront distribution (S3 as origin). CloudFront gives you a global CDN, HTTPS, and lets the bucket stay private.
Why does my React app 404 on refresh when hosted on S3/CloudFront?
Client-side routes like /dashboard have no matching file in S3. Configure CloudFront custom error responses to return /index.html with a 200 for 403/404, so the React router handles the path.
How do I make a new deploy show up on CloudFront?
Create an invalidation (aws cloudfront create-invalidation --paths '/*', or just /index.html). Hashed asset files change names each build so they don't need invalidating; the HTML entry point does.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →