CloudFront

CloudFront is AWS’s global Content Delivery Network (CDN) that accelerates delivery of static + dynamic content using 600+ global edge locations. It’s deeply integrated with S3, ALB, API Gateway, Lambda@Edge, and CloudFront Functions.
Author

Benedict Thekkel

🔥 1. Core Concepts

1.1 Edge Locations

  • Global network of POPs (Points of Presence)
  • Reduce latency by serving cached content closest to the user
  • Automatically choose the best route

1.2 Distributions

A CloudFront distribution is the configuration object that defines:

  • Where origin content comes from (S3, ALB, API Gateway, EC2, Custom Origin)
  • What caching rules apply
  • What security settings apply.

There are two types:

Type Description
Web distribution Standard distribution for websites, APIs, static/dynamic assets
RTMP distribution Deprecated

⚙️ 2. Origins

CloudFront can pull content from:

S3 Origin

  • Most common use
  • CloudFront acts as a secure front layer over S3
  • Supports Origin Access Control (OAC) for private buckets (replaces old OAI)

Custom Origin

Examples:

  • Application Load Balancer (ALB)
  • EC2
  • Any HTTP server
  • API Gateway endpoints
  • Containers on ECS/EKS
  • Even non-AWS servers

Origin Groups

  • Failover strategy (Primary → Secondary)
  • Used for high availability setups (e.g., multi-region S3)

🧠 3. Caching Model

CloudFront caches objects at edge locations based on:

  • Path pattern
  • Headers
  • Cookies
  • Query strings
  • Cache key policies

Cache Key = What determines uniqueness

Good practice:

Keep the cache key small unless needed → Reduce edge misses → Faster performance → Lower cost.

Examples:

Scenario Recommended Cache Key
Static assets URL path only
Authenticated API Path + Authorization header (or JWT claims)
Image resizing Path + width/height query params

⏱️ 4. TTL Rules

CloudFront respects:

  1. Cache-Control headers (max-age, s-maxage)
  2. Expires header
  3. If none provided → default TTL

Default TTL values (you can override):

  • Minimum TTL – 0 seconds
  • Default TTL – 24 hours
  • Maximum TTL – 1 year

🔐 5. Security Features

✔ Origin Access Control (OAC)

  • Lets CloudFront access private S3
  • Enforces signed requests
  • Recommended over OAI

✔ Signed URLs / Signed Cookies

To restrict content access:

  • Time-limited links
  • IP-limited access

✔ Geo restrictions

Block or allow countries.

✔ Custom SSL Certificates

Attach via ACM (must be in us-east-1).

✔ HTTPS-only enforcement

✔ AWS WAF Integration

Add WAF Web ACL for:

  • Rate limiting
  • IP allow/block
  • Bot Control
  • Managed rule sets

✔ Shield Standard (free)

DDoS protection included.


🤖 6. Edge Compute Options

6.1 CloudFront Functions

  • Ultra-light JavaScript

  • Runs at viewer request/response

  • Sub-millisecond latency

  • Cheap (millions of executions per $)

  • Use cases:

    • URL rewrites
    • Header manipulation
    • AB testing
    • Simple redirects

6.2 Lambda@Edge

  • Full Node.js/Python runtime

  • Runs at both viewer and origin events

  • Higher latency & cost

  • Use cases:

    • Authentication at the edge
    • Dynamic HTML rewriting
    • Personalization at the edge
    • Multi-origin routing logic

🛰️ 7. Common CloudFront Architectures

7.1 S3 Static Website Hosting

  • S3 bucket (private)
  • CloudFront distribution with OAC
  • React/Vite build output uploaded
  • Cache invalidation on deployments

7.2 CDN for a Backend API

  • Origin: ALB or API Gateway
  • Improves global latency
  • Adds WAF + caching

7.3 Multi-origin Routing

  • /api → API Gateway
  • /static → S3
  • /images → S3 + image optimization lambda@edge

📤 8. Invalidation

When you want to invalidate cached files:

Types:

Pattern Usage
/index.html Deploying new frontend
/static/* Full static asset flush
/* Full wipe (expensive)

Invalidations cost money (free up to 1,000 paths monthly).

Better strategy:

  • Use cache-busting filenames (React/Vite do this automatically).

💰 9. Pricing (Simplified)

Pricing depends on:

  • Region
  • Data out
  • Requests
  • Functions@edge usage
  • Invalidation count

Approx (Australia users served from Sydney POP):

  • ~$0.085 per GB (AU → user)
  • $0.60 per 1M HTTP requests
  • CloudFront Functions: ~$0.10 per 1M invocations
  • Lambda@Edge: similar to Lambda but billed per region

📦 10. Real-World Best Practices (2025)

⭐ Use OAC (not OAI) for S3

⭐ Use CloudFront Functions for rewrites/AB tests

⭐ Use WAF for all public-facing traffic

⭐ Compress (gzip/brotli) content before S3 upload

⭐ Use immutable caching for assets:

Cache-Control: public, max-age=31536000, immutable

⭐ Deploy versioned builds (avoid invalidations)

⭐ Prefer API Gateway as origin for serverless backends

⭐ Enable HTTP/3


📘 11. CloudFront Logging + Monitoring

Logs

  • Standard logs → S3
  • Realtime logs → Kinesis Data Streams

Metrics

  • Miss rate
  • Hit rate
  • 5xx errors
  • 4xx spikes
  • Throughput and latency

🧩 12. When to Use CloudFront

Use CloudFront if: ✓ You serve users globally ✓ You serve static assets ✓ You want to hide your S3 bucket ✓ You want DDoS + WAF protection ✓ You want to accelerate APIs ✓ You want caching for performance + cost savings


🆚 13. CloudFront vs Alternatives

Feature CloudFront Cloudflare Akamai
Edge count ~600+ ~300+ ~4,000+
Edge compute Lambda@Edge Workers (strong) EdgeWorkers
AWS native
Custom SSL Free Free Paid
WAF Excellent Excellent Enterprise

CloudFront wins for AWS-native architectures.


📦 14. CloudFront with React / Next.js / Vite

S3 + CloudFront

  • Upload /dist
  • Invalidate /index.html

Server-Side Rendered Apps

  • Next.js → via S3+Lambda@Edge or Amplify Hosting

  • CloudFront handles:

    • Static assets
    • API
    • Image optimization

📄 15. Example CloudFront Config for a Modern Web App

Behaviors

Path Origin TTL Notes
/assets/* S3 1 year immutable
/api/* ALB/API GW 0 (no cache) Include auth headers
/ S3 0–10 seconds HTML not cached long
/_next/* S3 or Lambda@Edge 1 year Next.js

Security

  • OAC enabled
  • WAF ACL attached
  • HTTPS-only
  • Geo restriction optional

🎯 If you want, I can also generate:

✅ A CloudFront + S3 deployment guide ✅ A Terraform/CDK template ✅ A fully working React/Vite hosting pipeline ✅ Cache optimization plan for your specific app ✅ Invalidations strategy ✅ Architecture diagram for your project

Just tell me!

CloudFront + S3 deployment guide

0. Assumptions

  • React app created with something like:

    npx create-react-app my-app
    # or
    npm create vite@latest my-app -- --template react
  • You want:

    • S3 = static file origin
    • CloudFront = CDN + HTTPS + cache + custom domain (optional)

I’ll break it into:

  1. Build React app
  2. Create & configure S3 bucket
  3. Upload build to S3
  4. Create CloudFront distribution
  5. (Optional) Custom domain + HTTPS
  6. (Optional) Cache invalidation on deploy
  7. Bonus: small deploy script

1️⃣ Build the React app

From your React project root:

# CRA
npm run build
# or Vite
npm run build

You’ll get something like:

  • CRA: build/
  • Vite: dist/

That folder is what goes to S3.


2️⃣ Create the S3 bucket

In the AWS Console:

  1. Go to S3 → Create bucket
  2. Bucket name: e.g. my-react-app-prod
  3. Region: usually same as rest of your stack (e.g. ap-southeast-2)
  4. Uncheck “Block all public access” only if you plan to use S3 website hosting directly. For CloudFront best practice, keep it blocked and use OAC.

We’ll assume private bucket + CloudFront Origin Access Control (OAC).

2.1 Folder layout in S3

Upload your built assets (we’ll do the actual upload in step 3), so the bucket will look like:

  • index.html
  • asset-manifest.json (CRA)
  • static/... or assets/... (bundles, CSS, etc.)

No subfolder required unless you want a v1/, v2/ structure.


3️⃣ Upload build artifacts to S3

Simplest (manual) way:

  • In S3 bucket → Upload → drag/drop contents of build/ (or dist/), not the folder itself.

CLI version (nice for future automation):

# Using AWS CLI v2
aws s3 sync build/ s3://my-react-app-prod/ \
  --delete \
  --exclude "*.map"

Or for Vite:

aws s3 sync dist/ s3://my-react-app-prod/ --delete --exclude "*.map"

--delete removes old files that no longer exist locally, keeping the bucket in sync.


4️⃣ Create the CloudFront distribution

In the AWS Console:

  1. Go to CloudFront → Create distribution

  2. Origin section:

    • Origin domain: pick your S3 bucket (my-react-app-prod.s3.amazonaws.com or the “Bucket” option).

    • Origin access:

      • Choose Origin access control settings (recommended)
      • Create new OAC if you don’t have one yet.
    • After creating the distribution, CloudFront will show you a bucket policy snippet. Copy it and apply it to the S3 bucket so that only CloudFront can read from it.

  3. Default cache behavior

    • Viewer protocol policy: Redirect HTTP to HTTPS

    • Allowed HTTP methods:

      • For static SPA: GET, HEAD is enough.
    • Cache policy:

      • Start with CachingOptimized or CachingDisabled while developing.
      • Later, use a custom policy with long TTLs for /static/* or /assets/*.
  4. Settings

    • Price class: e.g. Use only North America and Europe or Use All Edge Locations (depends on your users).

    • Alternate domain name (CNAME): leave blank for now (we’ll set custom domain in step 5).

    • SSL/TLS certificate: default CloudFront certificate for *.cloudfront.net (change later if you add a custom domain).

    • Default root object: set this to index.html

      This is crucial so that hits to https://dXXXX.cloudfront.net/ return your React app.

  5. Create distribution

Once status is Deployed, you’ll have a URL like:

https://d123abcd.cloudfront.net/

Open it and you should see your React app.


5️⃣ SPA routing (React Router) with CloudFront

If you use React Router (/dashboard, /settings, etc.), hitting those paths directly will 404 at the origin unless you handle them.

Goal: For any unknown path, CloudFront should serve index.html so the client-side router can handle it.

You have a few options:

Option A: SPA-style 404 fallback via Custom Error Response

  1. Edit your CloudFront distribution → Error pages tab → Create custom error response
  2. For HTTP error code: 404
  3. Customize error response: Yes
  4. Response page path: /index.html
  5. HTTP response code: 200

Now if CloudFront/origin returns a 404, it serves index.html with a 200 response. React Router then handles the route.


6️⃣ (Optional) Custom domain + HTTPS via ACM

If you want https://app.mycompany.com:

  1. Get a certificate in ACM

    • Region: us-east-1 (CloudFront requirement).
    • Request public certificate → include your domain (e.g. app.mycompany.com).
    • Validate via DNS (CNAME).
  2. Update CloudFront distribution

    • In distribution Settings:

      • Alternate domain name (CNAME): app.mycompany.com
      • Custom SSL certificate: choose the ACM cert you created.
  3. Configure DNS (Route 53 or your DNS host)

    • Add a CNAME or ALIAS record:

      • Name: app.mycompany.com
      • Value: your CloudFront domain d123abcd.cloudfront.net

Once DNS propagates, https://app.mycompany.com will serve your app via CloudFront.


7️⃣ Cache invalidation for React deploys

Because React build outputs are usually fingerprinted (e.g. main.4c5f2d.js), you rarely need to invalidate everything.

Minimum you should invalidate on each deploy:

  • /index.html

In CloudFront console:

  • Invalidations → Create invalidation
  • Path: /index.html

CLI:

aws cloudfront create-invalidation \
  --distribution-id D123ABC456 \
  --paths "/index.html"

If you don’t have hashed filenames (not recommended), you might temporarily invalidate /static/* or /*.


8️⃣ Simple deploy script (bash)

You can wrap all of this into a tiny script and plug into CI (GitHub Actions, etc.)

#!/usr/bin/env bash
set -euo pipefail

DISTRIBUTION_ID="D123ABC456"
BUCKET_NAME="my-react-app-prod"
BUILD_DIR="build"  # or dist for Vite

echo "▶ Building React app..."
npm run build

echo "▶ Syncing ${BUILD_DIR}/ to s3://${BUCKET_NAME}/ ..."
aws s3 sync "${BUILD_DIR}/" "s3://${BUCKET_NAME}/" \
  --delete \
  --exclude "*.map"

echo "▶ Creating CloudFront invalidation for /index.html ..."
aws cloudfront create-invalidation \
  --distribution-id "${DISTRIBUTION_ID}" \
  --paths "/index.html"

echo "✅ Deploy complete."

Hook this into:

  • make deploy
  • GitHub Actions workflow (run on push to main)

9️⃣ Summary Checklist

Here’s the “did I forget anything?” checklist:

Back to top