Host your React application (or Vue, Angular, Svelte) on an EC2 server? You could. You’ll install Nginx, set up a route, and serve your index.html.
But you probably shouldn't.
The "Static" Revelation
Here is the secret: Your React app, after you run npm run build, isn't an "app" anymore. It's just a pile of files. A .html file, some .js files, and a few images.
EC2 servers are designed to compute. They are thinking machines. S3 buckets are designed to store. They are digital warehouses.
The Analogy 🧠
Asking an EC2 server to hold your HTML file is like hiring a PhD Mathematician to hold your coat.
- It works: They can definitely hold the coat.
- The cost: You are paying a high salary for a simple task.
- The waste: They should be solving complex equations (running backend logic), not acting as a coat rack.
S3 is a coat rack. It's cheap, it's simple, and it does one thing perfectly: hold stuff.
The Math (EC2 vs S3)
- EC2 (t3.small): ~$15.00 / month.
- S3: ~$0.023 / GB.
If your website is 100MB (which is huge), hosting it on S3 costs you $0.002 per month.
You read that right. Two tenths of a penny.
The Architecture: S3 + CloudFront
What is a CDN?
Imagine if you had to drive to Italy every time you wanted a slice of pizza. That's a normal server. A CDN (Content Delivery Network) is like having a pizza delivery guy waiting on your street corner. It copies your website to servers all over the world.
- S3: Holds the "Master Copy" of your files.
- CloudFront: Copies them to hundreds of Edge Locations (local servers near your users).
- You: Get a site that loads instantly, whether you are in Tokyo, New York, or London.
Step 1: Build the Artifacts
First, create your production build.
npm run build # Creates a 'dist' or 'build' folder
Open that folder. See that index.html? That’s gold.
Step 2: The Bucket
- Go to AWS Console > S3.
- Create Bucket.
- Name:
my-awesome-react-site(Must be globally unique). - Public Access: Uncheck "Block all public access".
- Warning: AWS will scream at you. "Are you sure? This is dangerous!"
- Answer: Yes. This is a public website. We want people to see it.
- Create.
Uploading
Go into your new bucket and upload the contents of your build folder. Not the folder itself, but the files inside it (index.html, static/, etc.).
Enable Static Hosting
- Go to the Properties tab.
- Scroll to the very bottom: Static website hosting.
- Click Edit -> Enable.
- Index document:
index.html. - Error document:
index.html(This is crucial for React Router! If someone visits/about, S3 doesn't know that file exists, so we tell it "Just give them index.html, React will handle the rest").
Step 3: The Permissions
Right now, your files are there, but no one can read them. We need a Bucket Policy.
- Go to the Permissions tab.
- Scroll to Bucket policy -> Edit.
- Paste this (replace
YOUR_BUCKET_NAME):
{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*" } ] }
Now, go back to the Properties tab, find the "Bucket website endpoint" at the bottom, and click it.
Boom. Your site is live. 🚀
Step 4: Turbocharge with CloudFront (Optional but Recommended)
The S3 link is ugly (http://my-bucket.s3-website-us-east-1...) and it's not HTTPS.
- Go to CloudFront -> Create Distribution.
- Origin Domain: Select your S3 bucket.
- Viewer Protocol Policy: Redirect HTTP to HTTPS.
- Create.
CloudFront will give you a new URL (e.g., d12345.cloudfront.net). This URL is SSL-secured, HTTP/2 enabled, and cached globally.
Step 5: The Custom Domain (The Final Polish)
Right now, your site is live at d12345.cloudfront.net. It works, but it looks unprofessional. You want yourwebsite.com.
This involves three players. It sounds complicated, but think of it like updating your contact info in a phone book:
- Route 53 (The Phone Book): Matches your name (
yourwebsite.com) to your actual location (the CloudFront server). - ACM (The ID Card): Proves you actually own the name.
- CloudFront (The House): The place where your website lives.
5.1 Get the Certificate (ACM)
We need a free SSL certificate to verify we own the domain.
- Go to AWS Console > Certificate Manager (ACM).
⚠️ CRITICAL WARNING ⚠️ You MUST switch your region to US East (N. Virginia) in the top right corner of the AWS Console.
Why? CloudFront is a global service, but its "brain" lives in Virginia. If you create the certificate in Ohio or London, CloudFront simply won't see it. This is the #1 reason people get stuck.
- Request Certificate.
- Enter your domain:
yourwebsite.comand*.yourwebsite.com(for subdomains). - Validation Method: DNS validation (easiest if you use Route 53).
- Click Create records in Route 53 after it's created. This proves you own the domain.
- Wait a few minutes for the status to change to Issued.
5.2 Link Config to CloudFront
Now tell CloudFront about your domain.
- Go back to your CloudFront Distribution -> Edit.
- Alternate Domain Names (CNAMEs): Add
yourwebsite.com. - Custom SSL Certificate: Select the certificate you just created in step 5.1.
- Save Changes.
5.3 Point DNS to CloudFront (Route 53)
Finally, tell the internet where yourwebsite.com lives.
- Go to Route 53 > Hosted Zones.
- Click your domain.
- Create Record.
- Name: Leave blank (for root domain).
- Type: A - Route traffic to an IPv4 address.
- Alias: Toggle this switch to Yes.
- Route traffic to: Alias to CloudFront Distribution.
- Choose your distribution from the list.
- Create records.
What just happened?
We created an Alias Record.
Normally, DNS points to an IP address (like 192.168.1.1). But CloudFront IPs change all the time. An Alias says "Hey, wherever CloudFront is right now, just send traffic there." It is dynamic and essentially free.
Wait 5 minutes. Visit yourwebsite.com.
Congratulations. You are now hosting a globally distributed, SSL-secured React application for less than the price of a gumball.
Summary
You just moved from a $15/month server to a specialized, serverless hosting architecture that costs pennies and is faster than any single server could ever be.
Backend goes on EC2. Frontend goes on S3.
This is the way.