AWS Builder Center

Host a Modern Web App on AWS for Pennies: S3 + CloudFront

Many junior developers default to running an EC2 instance or a container to host a portfolio or frontend side project. But if your app is built with React, Vue, Vite, or plain HTML/CSS, hosting on a traditional server means paying for idle CPU cycles. The battle-tested alternative: Amazon S3 for static storage paired with Amazon CloudFront for global content delivery and free SSL.

Why This Stack Wins

  • Cost: Practically $0 per month for personal portfolios or prototypes.
  • Speed: CloudFront caches assets at edge locations worldwide, drastically cutting latency.
  • Maintenance: Zero operating system updates, zero server patches, zero downtime from memory leaks.

Step 1: Set Up Your S3 Bucket

  1. Open the Amazon S3 Console and click Create bucket.
  2. Pick a unique name (e.g., my-dev-portfolio-2026) and your preferred AWS region.
  3. Keep Block all public access enabled. We will route all traffic securely through CloudFront rather than exposing the bucket directly to the internet.

Step 2: Build and Upload Your Assets

Run your framework’s build command locally to generate your static output folder (usually dist or build):
1
2
3
4
5
# Build your static files
npm run build

# Sync the output directly to S3 via the AWS CLI
aws s3 sync ./dist s3://my-dev-portfolio-2026/ --delete

Step 3: Distribute Globally with CloudFront

  1. Open the CloudFront Console and click Create distribution.
  2. Origin Domain: Select your newly created S3 bucket from the dropdown.
  3. Origin Access Control (OAC): Select Origin access control settings (recommended) and click Create new OAC. This generates a policy so only CloudFront can read from your S3 bucket.
  4. Viewer Protocol Policy: Set to Redirect HTTP to HTTPS.
  5. Default root object: Enter index.html.
  6. Click Create distribution.

Step 4: Apply the S3 Bucket Policy

CloudFront provides a ready-made bucket policy after creation. Copy it and navigate back to your bucket:
  1. In S3, open your bucket's Permissions tab.
  2. Under Bucket policy, click Edit and paste the policy:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"Version": "2012-10-17",
"Statement": {
"Sid": "AllowCloudFrontServicePrincipalReadOnly",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-dev-portfolio-2026/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<ACCOUNT_ID>:distribution/<DISTRIBUTION_ID>"
}
}
}
}
Replace <ACCOUNT_ID> and <DISTRIBUTION_ID> with your specific values.

Quick Pro-Tip: Single Page App (SPA) Routing

If you are using React Router or Vue Router, a direct reload on a nested route (e.g., /about) can throw a 403 Forbidden or 404 Not Found from S3.
Fix it in one click:
  • In your CloudFront distribution, open the Error pages tab.
  • Click Create custom error response.
  • Map HTTP error codes 403 and 404 to return /index.html with an HTTP response code of 200: OK.

Over to You

Setting up this architecture teaches core cloud concepts—storage, caching, edge networks, and IAM policies—while keeping your monthly bill at zero.
Builders: Have you migrated a static site to S3 and CloudFront, or are you still running it on a VPS? What challenges did you encounter? Let's discuss in the comments below!
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