DEV Community

TANUJ AJ
TANUJ AJ

Posted on

Day-1 Cloud Workshop

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

But when I looked at my AWS console, the actual bucket I was configuring was:

portfolio-tanuj
Enter fullscreen mode Exit fullscreen mode

That caused confusion while creating the bucket policy.

AWS was telling me:

Policy has invalid resource
Enter fullscreen mode Exit fullscreen mode

The reason was simple.

The policy resource has to reference the actual bucket name.

So:

arn:aws:s3:::portfolio-tanuj/*
Enter fullscreen mode Exit fullscreen mode

instead of:

arn:aws:s3:::tanuj-bucket-portfolio/*
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
];
Enter fullscreen mode Exit fullscreen mode

And projects were represented as data:

const projects = [
    {
        title: "CLOTE",
        description: "...",
        tech: "Private Cloud • File Management"
    }
];
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

and then:

npm run build
Enter fullscreen mode Exit fullscreen mode

Vite generated:

dist/
│
├── index.html
└── assets/
    ├── ...
    └── ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

7. Adding CloudFront

Then I wanted to put CloudFront in front of S3.

The architecture became:

                    ┌──────────────┐
                    │    Browser   │
                    └──────┬───────┘
                           │
                           ▼
                    ┌──────────────┐
                    │  CloudFront  │
                    └──────┬───────┘
                           │
                           ▼
                    ┌──────────────┐
                    │      S3      │
                    │   index.html │
                    │    assets/   │
                    └──────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

At first I had no idea why AWS was asking for:

index.html/index.html
Enter fullscreen mode Exit fullscreen mode

My S3 bucket clearly contained:

index.html
Enter fullscreen mode Exit fullscreen mode

not:

index.html/index.html
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

That was the mistake.

CloudFront already knew that the default root object was:

index.html
Enter fullscreen mode Exit fullscreen mode

And I had also told the origin to use:

/index.html
Enter fullscreen mode Exit fullscreen mode

So CloudFront effectively ended up requesting:

/index.html/index.html
Enter fullscreen mode Exit fullscreen mode

S3 obviously couldn't find that object.

The error suddenly made sense.


10. The Fix

I changed the CloudFront origin path from:

/index.html
Enter fullscreen mode Exit fullscreen mode

to:

(empty)
Enter fullscreen mode Exit fullscreen mode

And kept the CloudFront default root object as:

index.html
Enter fullscreen mode Exit fullscreen mode

So the configuration became conceptually:

CloudFront

Default root object:
index.html

Origin:
portfolio-nith S3 website endpoint

Origin path:
(empty)
Enter fullscreen mode Exit fullscreen mode

Now the request flow made sense:

/
 ↓
CloudFront
 ↓
index.html
 ↓
S3
 ↓
portfolio
Enter fullscreen mode Exit fullscreen mode

instead of:

/
 ↓
CloudFront
 ↓
index.html
 ↓
/index.html
 ↓
index.html/index.html ❌
Enter fullscreen mode Exit fullscreen mode

11. Why I Had to Invalidate CloudFront

Even after fixing the configuration, CloudFront can still have cached responses.

So I created a CloudFront invalidation:

/*
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

I had to understand bucket permissions and public access.

Bucket policy

Policy has invalid resource
Enter fullscreen mode Exit fullscreen mode

I was referencing the wrong bucket name.

CloudFront

NoSuchKey
Key: index.html/index.html
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/         │
                 └───────────────────┘
Enter fullscreen mode Exit fullscreen mode

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

react #aws #cloudfront #s3 #webdevelopment #javascript #vite #beginners #devjourney #learninginpublic

Top comments (0)