I Built and Deployed My First React Portfolio on AWS S3 + CloudFront — And Broke It Along the Way
As a Computer Science student, I’ve been trying to move away from just writing code by following tutorials and actually understand how things work by building and deploying projects.
One of the things I wanted to build was a simple personal portfolio.
It sounded easy:
Build a React portfolio → upload it to AWS → get a public URL.
It wasn't quite that simple.
I ran into permissions issues, S3 configuration problems, CloudFront errors, and eventually discovered that I had accidentally configured CloudFront to look for:
index.html/index.html
This is the story of how I built it, broke it, debugged it, and finally understood what was actually happening.
1. Starting With a Simple HTML Portfolio
I initially created my portfolio using a single:
index.html
I kept the design intentionally simple.
The portfolio contained:
- About Me
- Technical Skills
- Projects
- Education
- Areas of Interest
- Contact
Some of the projects and areas I included were:
- CLOTE — self-hosted cloud file management
- Automated Data Intelligence System
- Graph Algorithm Visualizer
- ML Ad-Click Prediction
- City-Specific Smart Traffic System
- Knowledge-Based Systems
At this point, everything worked locally.
I could simply open the HTML file in my browser and see the portfolio.
But then I wanted to actually deploy it.
2. Moving to AWS S3
I decided to use Amazon S3 for hosting.
I created an S3 bucket and uploaded my portfolio.
The object looked like:
s3://portfolio-tanuj/index.html
My first assumption was:
"The file is uploaded, so anyone should be able to open it."
That assumption was wrong.
When I tried accessing the website, I received:
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
</Error>
3. First Struggle — S3 AccessDenied
This was my first real AWS deployment problem.
The HTML itself wasn't the issue.
The problem was with S3 permissions and website access configuration.
I had to understand the relationship between:
S3 Bucket
↓
Object
↓
Permissions
↓
Static Website Hosting
I configured static website hosting and worked through the public-access configuration and bucket policy.
The important part of the bucket policy was allowing public GetObject access:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::portfolio-nith/*"
}
]
}
But then I ran into another issue.
4. My Bucket Name Wasn't What I Thought
At one point I was working with:
tanuj-bucket-portfolio
But when I looked at my AWS console, the actual bucket I was configuring was:
portfolio-tanuj
That caused confusion while creating the bucket policy.
AWS was telling me:
Policy has invalid resource
The reason was simple.
The policy resource has to reference the actual bucket name.
So:
arn:aws:s3:::portfolio-tanuj/*
instead of:
arn:aws:s3:::tanuj-bucket-portfolio/*
That was a small mistake, but it taught me something important:
Don't blindly copy AWS configuration. Understand what resource you're actually configuring.
5. Then I Wanted to Use React
Once I had the basic portfolio working, I decided to convert it into a React application.
Instead of one large HTML file, I moved to a React + Vite structure:
portfolio/
│
├── index.html
├── package.json
│
└── src/
├── main.jsx
└── style.css
I used React to render the portfolio sections and JavaScript arrays for things like skills and projects.
For example:
const skills = [
"Python",
"C / C++",
"Data Structures & Algorithms",
"Machine Learning",
"TensorFlow",
"NumPy",
"Pandas",
"Docker"
];
And projects were represented as data:
const projects = [
{
title: "CLOTE",
description: "...",
tech: "Private Cloud • File Management"
}
];
This was my first step toward understanding how a real frontend project is structured rather than putting everything into one HTML file.
6. Understanding npm run build
This was another important part.
I initially thought:
"Since it's React, I need a server running on AWS."
Not necessarily.
React can be built into static files.
I ran:
npm install
and then:
npm run build
Vite generated:
dist/
│
├── index.html
└── assets/
├── ...
└── ...
This was a major realization for me.
The browser doesn't need my React source code or Node.js running on S3.
The production build becomes static assets that can be served by a web server.
So the deployment becomes:
React source
↓
npm run build
↓
dist/
↓
AWS S3
7. Adding CloudFront
Then I wanted to put CloudFront in front of S3.
The architecture became:
┌──────────────┐
│ Browser │
└──────┬───────┘
│
▼
┌──────────────┐
│ CloudFront │
└──────┬───────┘
│
▼
┌──────────────┐
│ S3 │
│ index.html │
│ assets/ │
└──────────────┘
This was the point where things got interesting.
8. The CloudFront 404 Error
Instead of my portfolio, CloudFront returned:
404 Not Found
Code: NoSuchKey
Message: The specified key does not exist.
Key: index.html/index.html
At first I had no idea why AWS was asking for:
index.html/index.html
My S3 bucket clearly contained:
index.html
not:
index.html/index.html
So I started looking at the CloudFront configuration.
9. Finding the Actual Problem
I checked the CloudFront origin configuration.
And there it was.
My origin looked like:
Origin type: S3 static website
Origin path: /index.html
That was the mistake.
CloudFront already knew that the default root object was:
index.html
And I had also told the origin to use:
/index.html
So CloudFront effectively ended up requesting:
/index.html/index.html
S3 obviously couldn't find that object.
The error suddenly made sense.
10. The Fix
I changed the CloudFront origin path from:
/index.html
to:
(empty)
And kept the CloudFront default root object as:
index.html
So the configuration became conceptually:
CloudFront
Default root object:
index.html
Origin:
portfolio-nith S3 website endpoint
Origin path:
(empty)
Now the request flow made sense:
/
↓
CloudFront
↓
index.html
↓
S3
↓
portfolio
instead of:
/
↓
CloudFront
↓
index.html
↓
/index.html
↓
index.html/index.html ❌
11. Why I Had to Invalidate CloudFront
Even after fixing the configuration, CloudFront can still have cached responses.
So I created a CloudFront invalidation:
/*
This tells CloudFront to remove the cached objects so that the next request can fetch the updated content.
12. What I Actually Learned
The biggest thing I learned from this wasn't React.
It wasn't S3.
It wasn't even CloudFront.
It was debugging.
Before this, when something didn't work, my first instinct was often:
"The code must be wrong."
But this project showed me that problems can exist at completely different layers.
For example:
Frontend
↓
React
↓
Build
↓
Static files
↓
S3
↓
Permissions
↓
CloudFront
↓
Caching
↓
Browser
A website can have perfectly valid React code and still return a 404 because the infrastructure configuration is wrong.
13. My Struggles
These were some of the problems I faced:
S3
AccessDenied
I had to understand bucket permissions and public access.
Bucket policy
Policy has invalid resource
I was referencing the wrong bucket name.
CloudFront
NoSuchKey
Key: index.html/index.html
I had incorrectly configured the origin path.
Caching
Even after fixing the configuration, CloudFront could still serve an old cached response.
14. What I Would Do Differently Next Time
If I were starting this deployment again, my process would be:
1. Build React application
↓
2. Test locally
↓
3. npm run build
↓
4. Check dist/
↓
5. Upload dist contents to S3
↓
6. Configure S3
↓
7. Configure CloudFront
↓
8. Test CloudFront
↓
9. Check logs/errors if something fails
↓
10. Invalidate cache when required
And most importantly:
I would change one configuration at a time and test after each change.
15. Final Architecture
The final concept I learned is:
┌───────────────────┐
│ User │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ CloudFront │
│ CDN │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ S3 Bucket │
│ │
│ index.html │
│ assets/ │
└───────────────────┘
My React application is built into static files and served through AWS infrastructure.
Final Thoughts
This was a relatively small project, but it gave me something more valuable than just a portfolio.
It gave me experience with the uncomfortable part of development:
things not working.
I had to read errors, inspect configuration, understand what AWS was actually doing, find the mismatch, and fix it.
The index.html/index.html error in particular was frustrating at first.
But once I understood why it happened, it became one of the most useful lessons from the project.
I'm still learning AWS, React, and software development, but I'm slowly trying to replace:
"Copy the code and hope it works"
with:
"Understand what each component is doing, then debug when it doesn't."
And I think that's a much better way to learn development.
Tech Stack
Frontend
- React
- Vite
- HTML
- CSS
- JavaScript
Cloud
- Amazon S3
- Amazon CloudFront
Development
- VS Code
- npm
- Git/GitHub
Top comments (0)