AWS Builder Center

Hosting a Frontend-Only Web App on AWS with S3 and CloudFront

Many web apps need no backend at all. A React, Vue, or plain HTML/JS app that runs fully in the browser is just a folder of static files. This guide shows how to host one on AWS so it’s fast, HTTPS-enabled, and cheap to run.

Exploring Things

What We’ll Build

  • An S3 bucket that stores the app’s files (kept private)
  • A CloudFront distribution that serves them globally over HTTPS
  • Origin Access Control (OAC) so only CloudFront can read the bucket
  • Optional: a custom domain with an ACM certificate

Why Not Make the Bucket Public?

S3 can serve a website directly from a public bucket, but that has two drawbacks: it only supports HTTP on the website endpoint, and public buckets are a common security mistake. The better pattern is a private bucket with CloudFront in front.

Step 1: Build Your App

Generate the production files:
bash
1
npm run build
This creates a dist or build folder containing index.html and your assets.

Step 2: Create the S3 Bucket

  1. Open the S3 console and choose Create bucket.
  2. Pick a unique name and a region close to you.
  3. Leave Block all public access turned on.
  4. Upload the contents of your build folder.
Or use the CLI:
bash
1
aws s3 sync ./dist s3://your-bucket-name --delete

Step 3: Create the CloudFront Distribution

  1. Open CloudFront and choose Create distribution.
  2. Set the origin to your S3 bucket.
  3. For origin access, choose Origin access control (OAC) and create a new one.
  4. Set Viewer protocol policy to Redirect HTTP to HTTPS.
  5. Set Default root object to index.html.
CloudFront then gives you a bucket policy to copy. Paste it into your bucket’s permissions so that only this distribution can read the files.

Step 4: Handle Client-Side Routing

If your app uses a router (for example /dashboard), refreshing that URL asks S3 for a file that doesn’t exist. Fix it with custom error responses in CloudFront:
  • For 403 and 404, return /index.html with HTTP status 200.
Now the app’s router handles the path, and refreshes work.

Step 5: Add a Custom Domain (Optional)

  1. Request a certificate in AWS Certificate Manager. For CloudFront, it must be in us-east-1.
  2. Validate it with DNS.
  3. Add your domain as an alternate domain name on the distribution and attach the certificate.
  4. Point your DNS record (Route 53 alias or CNAME) to the CloudFront domain.

Step 6: Deploy Updates

CloudFront caches your files, so updates may not appear immediately. After each deploy:
bash
1
2
aws s3 sync ./dist s3://your-bucket-name --delete
aws cloudfront create-invalidation --distribution-id YOUR_ID --paths "/*"
A better long-term habit is to use hashed filenames for assets (most build tools do this by default) and invalidate only index.html.

Cost and Security Notes

  • Static hosting is typically very inexpensive for small projects, but check current AWS pricing and free tier terms for your account.
  • Keep the bucket private and use OAC.
  • Set a budget alert in AWS Billing so surprises don’t happen.
  • Never put secrets in frontend code. Anything shipped to the browser is visible to users.

When to Use Amplify Hosting Instead

AWS Amplify Hosting handles builds, deploys from Git, and previews for you. If you want a simpler workflow and don’t need full control, it’s a good alternative. S3 + CloudFront is better when you want to understand each piece or need more control.

Conclusion

A frontend-only app doesn’t need servers. With a private S3 bucket, CloudFront, and OAC, you get HTTPS hosting that is secure, fast, and low-cost. Once this works, you can extend it with a custom domain, CI/CD, or an API behind the same distribution.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article