Skip to content
← BACK TO POSTS

Building Static Sites with AWS S3 and CloudFront

15 min read
#aws#static-sites#deployment

You already know how to build. Let's talk about deploying it right.

If you're a developer, running your own static site is one of the easiest ways to get something online while staying in your comfort zone: building, writing, and shipping.

The tricky part is deploying that site in a way that’s fast, reliable, secure, and inexpensive. Most developers do not want to manage servers for a small personal site, and many hosted options feel either too limited or too opaque.

Sure, you could use Squarespace, Wix, GitHub Pages, and plenty of others. But I prefer AWS S3 and CloudFront for the flexibility, cost, and control.

Now hear me out – AWS can feel complicated at first. But Amazon S3 paired with CloudFront solves this cleanly. You get global performance, strong reliability, and a setup that usually costs just a few dollars per month. You stay in code-land instead of wrestling with drag-and-drop editors. And while cheaper or one-click platforms exist, the AWS workflow gives you practical, transferable experience that pays off as your projects grow.

This guide walks through the entire workflow, no matter which static site generator you use, and gives you clear steps to go from zero to live.

Why Static Sites – and Why CloudFront + S3?

Static sites pair extremely well with AWS because the entire workflow is based on one simple output: a folder of static files. No matter which generator you use, the end result is the same – HTML, CSS, JavaScript, and assets that can be dropped directly into S3 and served globally through CloudFront.

Most tools output this in a predictable directory:

  • Next.js (static export) → out/
  • Hugo → public/
  • Astro → dist/
  • Jekyll → _site/
  • Eleventy → _site/
  • Angular (static build) → dist/

AWS does not know or care which tool produced them. The hosting process is completely generator agnostic.

Advantages of static sites

  • Fast page loads: low latency and global caching.
  • Minimal infrastructure: no servers, databases, or runtime environment to manage.
  • Low hosting cost: pay for storage and bandwidth, which is cheap for personal sites.
  • Simple, predictable deployments: a single sync and invalidate command.
  • Ideal for version control: the entire site is deployable from your git repository.
  • No backend to maintain: less security patching and fewer sleepless nights.

Static sites are perfect for blogs, documentation, personal sites, marketing pages, and lightweight product pages.

Most importantly, static sites keep you focused on the code and content rather than backend infrastructure. You write your pages, run a build, and deploy a folder. That is the entire workflow. Your energy stays where it belongs – designing, writing, and building.

Some generators emphasize content (Hugo, Jekyll, Eleventy). Others emphasize components or application structure (Next.js, Astro, Angular). But the AWS deployment workflow stays the same – build locally, upload to S3, and let CloudFront handle global delivery.

Getting Started with AWS

The first steps are all about security and setup. We'll harden your AWS account before we even touch S3.

Step 1: Build Your Site Locally

Start by building your site using whatever tool you are most comfortable with. All modern static site generators produce a directory of output files that can go straight into S3.

Keep your workflow straightforward:

  • Write your content
  • Build locally
  • Verify everything looks right
  • Prepare the output folder for deployment

Once your site builds cleanly, you are ready for the AWS part.

Step 2: Sign Up for AWS and Create a Secure SSO Admin

If you do not already have an AWS account, create one and sign in.

This first login uses your root account, which has unrestricted access to everything in AWS.

1. Harden the Root Account

The root account is too powerful for day-to-day work, and using it regularly increases the risk of accidental or malicious changes.

  • Store the root email address and password in a reputable password manager (e.g., 1Password).
  • Enable MFA on the root account (a hardware security key or an authenticator app is preferred).
  • Save any recovery codes or MFA backup methods in a secure place.

This root account is for getting in, getting out, and getting on with life. You will rarely use it after this, and you shouldn't. If something goes wrong, you'll need it. Otherwise, you'll do everything from the admin account we set up below.

2. Change the Default Region

Before creating anything, set your default region. Choose whatever is physically closest to you – as long as it is not us-east-1.

Friends do not let friends deploy in us-east-1. That region absorbs a huge share of AWS traffic and is known for occasional quirks. From a reliability and operational-sanity standpoint, you are usually better off in a less congested region. There are a few reasons to deploy there, but for a personal site, just choose something else.

3. Enable IAM Identity Center (SSO)

AWS best practice is to avoid using the root account for everyday tasks – IAM Identity Center will become your primary login method going forward.

  • In the AWS console, use the search bar at the top to find IAM Identity Center.
  • Open it and enable it if prompted.

4. Create Permission Sets

  • In IAM Identity Center, go to Permission sets.
  • Choose Predefined permission sets and add AdministratorAccess.
  • Create a second permission set using the predefined Billing permissions.

If you're a solo developer, combining an administrative role with a separate billing-permission set is usually the simplest option. In a larger team, you'll want to create more granular roles so not everyone has full admin or billing access.

5. Create Your SSO User

  • Still in IAM Identity Center, click Users then Add user.
  • Enter a username and an email address. (You can reuse the same email as the root account or use a different one – the root login remains separate).
  • Assign the new user the AdministratorAccess permission set.
  • Optionally (and recommended), assign the Billing permission set to this user as well so you can manage billing without using the root account.
  • Complete the setup and verify the email to activate your SSO user.

6. Save Your SSO Start URL and Stop Using Root

  • After setup, IAM Identity Center will display a user portal / start URL. Save this URL in your password manager and bookmarks.
  • Sign out of the root account.
  • Sign back in using the new SSO user you just created – this will be your primary way to access AWS from now on.

Step 3: Create Your S3 Bucket

Once you're logged in to your SSO admin account, search for S3 in the AWS console.

Click Create bucket and give it a name. This bucket will store your static build files.

Most defaults are correct, but double-check these:

  • Block all public access: keep this enabled
  • Server-side encryption: choose SSE-S3
  • Bucket key: enable it
  • Object lock: disable it

Static sites should always be served privately through CloudFront, not directly from S3.

After the bucket is created, open it and use Create folder to organize the files for your site. This is optional but helps keep things tidy if you ever host multiple projects.

Step 4: Create Your CloudFront Distribution

CloudFront is the global CDN layer that makes your site fast everywhere, not just near the S3 region.

Search for CloudFront in the AWS console.

Click Create distribution and configure:

  • Pricing class: the free tier is fine, but pay-as-you-go is generally inexpensive. For most personal blogs, pay-as-you-go ends up cheaper than AWS’s monthly pricing packages.
  • Name / description: anything you want
  • Application type: Single-page web app is fine for almost all static sites

Click Next when prompted for a domain name. We will add a custom domain later.

Configure Your Origin

  • Origin type: Amazon S3
  • Bucket: select your S3 bucket using Browse
  • Origin path: your folder name, starting with /your-folder (if you created one in S3)
  • Access: enable Allow private S3 bucket access to CloudFront
    • This step automatically creates an Origin Access Control (OAC) and applies the required bucket policy so only CloudFront can read from your bucket.

Use the recommended origin and cache settings – they are tuned for static assets.

Security Settings

Enable:

  • Security protections
  • Layer 7 DDoS attack protection (optional – this adds $30/month to your bill and is more relevant for large distributed applications than personal blogs. You can leave this unchecked.)

These give you AWS Shield-level protection by default.

Click Next, review the settings, and Create distribution.

CloudFront will take a few minutes to deploy.

Step 5: Set Default Root Object and Enable Bot Protection

Open your new CloudFront distribution.

Set the Default Root Object

Under General, click Edit.

Scroll to Default root object and enter: index.html

This ensures that hitting / loads your homepage correctly.

Bot Protection

Click the Security tab and enable Bot Protection. CloudFront groups automated traffic into categories. A strong starting point is:

Bot CategoryRecommended ActionWhy
Unverified bot actionBlockHigh-risk automation with no legitimate purpose.
Signal: Bot data centerBlockKnown bot-heavy networks that frequently generate abusive traffic.
Signal: Automated browserCAPTCHAOften scraping or scripted browsing – challenge to reduce noise without blocking legitimate tools.
Signal: Non-browser user agentCAPTCHATypically automation or scraping tools impersonating browsers.

Everything else can stay in Monitor mode. Watch your traffic and tighten categories individually when needed. We will enable logging, custom error pages, and additional hardening after the site is live.

Step 6: Set Up the AWS CLI and Deploy Your Build

We’re going to use the AWS CLI because it’s universal, scriptable, and works with every site generator.

Install the AWS CLI

  • macOS: brew install awscli
  • Linux: sudo apt install awscli (or use your distro’s package manager)
  • Windows: Start with installing Linux. But if you’re sticking with Windows, download and run the official AWS CLI MSI installer.

Configure SSO in the CLI

Once it is installed, open your terminal and run:

aws configure sso

You will see prompts like these:

PromptExplanation
SSO session name (Recommended)Put in a name for this account. This one doesn't matter too much if you only are logging in to this AWS instance.
SSO start URLThis is the URL you got when you set up your SSO user. Typically something like https://d-long-string.awsapps.com/start
SSO regionThe region you chose in step 2.
SSO registration scopes [sso:account:access]:The default value is correct, so push enter.

Press enter, and your web browser will open and authenticate your session. You'll be asked a couple more questions:

PromptExplanation
CLI default client RegionI typically just use the same region as above.
CLI default output formatNone is fine, just push enter.
CLI profile nameIf this is the only AWS account you'll be working in, type default and it makes the login commands a little easier.

Important CLI Commands

Your SSO session will expire periodically. When it does, run the below commands before deploying or running other AWS CLI commands.

  • If you named the profile default: aws sso login
  • To log in and refresh your SSO session for a given profile: aws sso login --profile your-profile-name

Deploying Your Build

1. Authenticate

Make sure you are authenticated:

aws sso login (If you used a non-default profile, add --profile your-profile-name.)

2. Push to S3

From the directory where your build folder lives, run the sync command. This command is fast because it only uploads new or modified files and removes files from S3 that are no longer in your local folder (due to the --delete flag):

aws s3 sync ./out s3://your-bucket/your-folder --delete

Adjust the folder name (./out) to match your generator's output.

If you want to get fancy, you can also create a wrapper around this command – for example, pnpm deploy:s3.

3. Invalidate CloudFront

Invalidating CloudFront is how you tell it to look for new files.

Invalidation forces CloudFront to refresh its global cache so users see the latest version of your site. It doesn't delete anything, it just tells CloudFront to go check for new files in your S3 bucket.

Here is the command to invalidate everything:

Invalidate CloudFront Cache
aws cloudfront create-invalidation \ --distribution-id YOUR_DISTRIBUTION_ID \ --paths "/*"

Replace YOUR_DISTRIBUTION_ID with the ID from your CloudFront distribution page. It can be found at the top of the page or in the URL.

Step 7: Test Your Site

CloudFront will take a few minutes to propagate globally.

Open the built-in CloudFront domain (e.g.: abcdefg1234567.cloudfront.net) and verify that your pages render correctly.

If you type /about and it loads /about.html, then your static generator already handles folder-style indexing. If not, you will want pretty URLs.

Step 8: Add a CloudFront Function for Pretty URLs

Static sites often generate files like:

/about.html and /contact.html

But most people expect:

/about and /contact

S3 does not automatically map /about to /about.html. CloudFront can do this for you using something called a CloudFront Function.

What is a CloudFront Function?

CloudFront Functions are small pieces of JavaScript that run on CloudFront’s global edge network before the request reaches your S3 bucket. They can:

  • Rewrite URLs
  • Add or remove headers
  • Redirect pages
  • Block automated traffic

They run extremely fast, cost almost nothing, and do not require any backend infrastructure. They are ideal for simple logic like URL rewriting. For heavier logic or accessing the response body, AWS also provides Lambda@Edge, but you won’t need that for static sites.

How to Create the Pretty URL Rewrite Function

1. Open CloudFront Functions

In the AWS console search bar, type CloudFront and open it. From the left menu, select Functions.

2. Create the Function

  • Click Create function
  • Name it something descriptive like pretty-urls
  • Choose CloudFront Function
  • Click Create function

3. Add the Rewrite Logic

Paste this code into the editor:

CloudFront Function - Pretty URLs
function handler(event) { var request = event.request; var uri = request.uri; // If the request already includes a file extension, leave it alone if (uri.includes('.')) { return request; } // If it ends with a slash, CloudFront will automatically try index.html if (uri.endsWith('/')) { return request; } // Otherwise append .html so /about becomes /about.html request.uri = uri + '.html'; return request; }

Click Save, then Publish.

Attach the Function to Your Distribution

  1. Open your CloudFront distribution
  2. Go to the Behaviors tab
  3. Edit the default behavior
  4. Scroll to Function associations
  5. Under Viewer Request, select your pretty-urls function
  6. Save the changes

Once deployed, CloudFront will rewrite clean URLs automatically.

If a user visits:

https://your-site.cloudfront.net/about

CloudFront will serve:

/about.html

Your users never see the rewrite – they just get the clean URL.

How Static Generators Differ

Some generators output nested index pages: /about/index.html and /blog/index.html. CloudFront automatically maps /about and /about/ to the correct file.

Others output flat files: /about.html and /blog.html. For these, the CloudFront Function handles the rewrite.

Either way, CloudFront ensures your URLs look clean and modern.

Step 9: Turn On Logging

Once everything works, enable CloudFront logging.

To stay privacy-friendly:

  • Anonymize IPs
  • Avoid logging cookies
  • Store logs in a dedicated S3 bucket

Logging costs pennies and gives you insight into performance and traffic patterns.

Step 10: Add Your Domain

Buy a domain (Route 53, Cloudflare, Namecheap, etc.), add it to your CloudFront distribution, and issue a certificate through AWS Certificate Manager.

Certificates are free and auto-renew when used with CloudFront.

Update your DNS records to point your domain to CloudFront.

Step 11: Deploy Changes and Invalidate the Cache

To update your site:

  1. Rebuild locally
  2. Upload new files to S3
  3. Invalidate CloudFront’s cache

Invalidation forces CloudFront to refresh its global cache so users see the latest version of your site.

Here is the command to invalidate everything:

aws cloudfront create-invalidation \ --distribution-id YOUR_DISTRIBUTION_ID \ --paths "/*"

Replace YOUR_DISTRIBUTION_ID with the ID from your CloudFront distribution page.

Quick Reference: Commands You’ll Use Regularly

A compact list of core commands you'll run as part of your normal workflow. You can also create pnpm or other wrapper commands around these to make your life easier.

AWS SSO Login
aws sso login
Upload to S3
aws s3 sync ./dist s3://your-bucket/your-folder --delete
Invalidate CloudFront Cache
aws cloudfront create-invalidation \ --distribution-id YOUR_DISTRIBUTION_ID \ --paths "/*"
List CloudFront Distributions
aws cloudfront list-distributions

List objects in your S3 deployment folder:

List S3 Bucket Files
aws s3 ls s3://your-bucket/your-folder/

Having these in one place makes updating your site as simple as rebuilding, syncing, and invalidating.

Go Forth and Stay Static

Running a static site on S3 and CloudFront gives you an ideal balance of performance, cost, and control. Everything stays fast globally, deployments are predictable, and you never have to think about maintaining servers or patching application infrastructure. You spend your time building your site, writing content, and refining your design – not worrying about uptime, scaling, or backend logic.

Beyond the basics, you can layer on additional capabilities at your own pace:

  • Analytics via CloudFront logs or privacy-friendly tools
  • Lightweight forms using services like Formspree or custom API routes
  • Deeper security using WAF or custom CloudFront Functions
  • CI/CD pipelines for automatic deployments

This workflow scales effortlessly from tiny personal blogs to documentation sites, product pages, and even complex component-driven frontends. Once the basics are in place, you gain a powerful foundation that grows with your projects and helps you build confidence with AWS.

The best part: the entire setup often costs just a few dollars per month while giving you enterprise-grade performance and flexibility. It’s a great investment in your future stack -- and a great way to learn real-world cloud fundamentals while shipping something you own and use.