Back to Insights
Technical Guide25 min read

How to Deploy a Static Website to AWS

A complete guide covering S3, CloudFront, Route 53, HTTPS — plus the fundamentals of how websites actually work.

What You'll Learn

Fundamentals

  • • What is a website, really?
  • • HTTP vs HTTPS — why it matters
  • • DNS — how domains find servers
  • • TLS/SSL — encryption explained
  • • CDN — why speed matters globally

AWS Deployment

  • • Building a Next.js static site
  • • S3 — storing your files
  • • CloudFront — CDN + HTTPS
  • • Route 53 — DNS management
  • • OG images — social media previews

Part 1: How Websites Work

What is a Website?

A website is just a collection of files — HTML, CSS, JavaScript, images — stored on a computer (server) that's connected to the internet 24/7. When you type a URL in your browser, your computer asks that server for those files, downloads them, and renders them on your screen.

# Simplified: What happens when you visit a website

1. You type: www.example.com

2. Your browser asks DNS: "What's the IP address of example.com?"

3. DNS responds: "It's 93.184.216.34"

4. Your browser connects to 93.184.216.34 and asks for files

5. Server sends back HTML, CSS, JS, images

6. Your browser renders the page

Static vs Dynamic: A static website serves the same files to everyone — perfect for marketing sites, blogs, and documentation. A dynamic website generates pages on the fly based on user data — think dashboards, e-commerce carts, or social media feeds. This guide focuses on static sites.

HTTP vs HTTPS — Why the "S" Matters

HTTP (Hypertext Transfer Protocol)

  • • Data sent in plain text
  • • Anyone on the network can read it
  • • No verification of server identity
  • • Browsers show "Not Secure" warning
  • • Port 80

HTTPS (HTTP Secure)

  • • Data is encrypted
  • • Only you and the server can read it
  • • Server identity verified by certificate
  • • Browsers show padlock icon
  • • Port 443

Why HTTPS is now mandatory

Google ranks HTTPS sites higher in search results. Browsers warn users about HTTP sites. Many web features (geolocation, camera, microphone) only work on HTTPS. Always use HTTPS.

TLS/SSL — How Encryption Works

TLS (Transport Layer Security) is the technology that makes HTTPS secure. You might also hear "SSL" — that's the older name (SSL was replaced by TLS, but people still say "SSL certificate").

How TLS works (simplified):

  1. 1. Handshake: Browser and server agree on encryption method
  2. 2. Certificate check: Server proves its identity with a certificate
  3. 3. Key exchange: Both sides create a shared secret key
  4. 4. Encrypted communication: All data is scrambled using the key

AWS Certificate Manager (ACM) provides free TLS certificates for domains you own. The certificate must be in the us-east-1 region to work with CloudFront.

DNS — The Internet's Phone Book

DNS (Domain Name System) translates human-readable domain names into IP addresses. Computers communicate using IP addresses (like 93.184.216.34), but humans remember names (like example.com).

Record TypePurposeExample
AMaps domain to IPv4 addressexample.com → 93.184.216.34
AAAAMaps domain to IPv6 addressexample.com → 2001:db8::1
CNAMEAlias to another domainwww → example.com
MXMail server for the domainmail.example.com
TXTText data (SPF, verification)v=spf1 include:_spf...

CDN — Making Websites Fast Globally

CDN (Content Delivery Network) copies your website to servers around the world. When someone visits your site, they download files from the server closest to them — not your origin server, which might be on the other side of the planet.

Without CDN:

User in Sydney → Server in Virginia (16,000 km) → 200ms latency

With CDN (CloudFront):

User in Sydney → Edge server in Sydney (50 km) → 10ms latency

AWS CloudFront has 450+ edge locations worldwide. It also handles HTTPS termination, caching, compression, and DDoS protection.

Part 2: The Tech Stack

This guide uses Next.js for building the site and AWS for hosting. Here's the complete stack:

Build Tools

  • Next.js 14 — React framework with static export
  • Tailwind CSS — Utility-first CSS framework
  • Framer Motion — Animations
  • Lucide React — Icons
  • Sharp — Image processing (OG images)

AWS Services

  • S3 — File storage (private bucket)
  • CloudFront — CDN + HTTPS
  • Route 53 — DNS management
  • ACM — Free TLS certificates
  • WAF — Firewall (optional)
package.json (key dependencies)
{
  "scripts": {
    "dev": "next dev",
    "prebuild": "node scripts/generate-og-images.js && node scripts/convert-svg-to-png.js",
    "build": "next build",
    "generate-og": "node scripts/generate-og-images.js && node scripts/convert-svg-to-png.js"
  },
  "dependencies": {
    "next": "14.1.0",
    "react": "^18",
    "tailwindcss": "^3.3.0",
    "framer-motion": "^11.0.0",
    "lucide-react": "^0.309.0"
  },
  "devDependencies": {
    "sharp": "^0.34.5"
  }
}
next.config.js (static export)
/** @type {import('next').NextConfig} */
const nextConfig = {
    output: 'export',           // Generates static HTML files
    trailingSlash: true,        // URLs end with / (e.g., /about/)
    images: {
        unoptimized: true       // Required for static export
    }
};

module.exports = nextConfig;

What does 'static export' mean?

Running npm run build generates an /out directory with pure HTML, CSS, and JS files. No server required — just upload these files to any web host (S3, Netlify, Vercel, etc.).

Part 3: OG Images for Social Media

OG (Open Graph) images are the preview cards you see when sharing a link on LinkedIn, Twitter, WhatsApp, or Slack. Without them, your links look plain and get fewer clicks.

Required meta tags in your HTML head:

<meta property="og:title" content="Your Page Title" />
<meta property="og:description" content="A brief description" />
<meta property="og:image" content="https://example.com/og/page-name.png" />
<meta property="og:url" content="https://example.com/page-name/" />

PNG is required — not SVG

Social media platforms (WhatsApp, LinkedIn, Twitter) do not render SVG images. You must convert OG images to PNG format. The recommended size is 1200×630 pixels.

Our approach: Generate SVGs programmatically (easy to template), then convert to PNG using Sharp.

scripts/generate-og-images.js
const fs = require('fs');
const path = require('path');

const articles = {
    'my-article': {
        title: 'Article Title Here',
        description: 'Brief description for social media preview.',
        category: 'Technical Guide'
    }
};

function generateSVG(slug, article) {
    return `<svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#1e293b"/>
      <stop offset="100%" style="stop-color:#0f172a"/>
    </linearGradient>
  </defs>
  
  <rect width="1200" height="630" fill="url(#bg)"/>
  <rect x="0" y="0" width="8" height="630" fill="#0d9488"/>
  
  <text x="60" y="80" font-family="system-ui" font-size="14" 
        font-weight="600" fill="#0d9488">${article.category.toUpperCase()}</text>
  
  <text x="60" y="180" font-family="system-ui" font-size="48" 
        font-weight="300" fill="white">${article.title}</text>
  
  <text x="60" y="280" font-family="system-ui" font-size="20" 
        fill="#94a3b8">${article.description}</text>
  
  <text x="60" y="560" font-family="system-ui" font-size="24" 
        font-weight="600" fill="white">Your Brand</text>
</svg>`;
}

// Generate all OG images
const outputDir = path.join(__dirname, '../public/og');
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });

Object.entries(articles).forEach(([slug, article]) => {
    const svg = generateSVG(slug, article);
    fs.writeFileSync(path.join(outputDir, `${slug}.svg`), svg);
    console.log(`Generated: ${slug}.svg`);
});
scripts/convert-svg-to-png.js
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');

const ogDir = path.join(__dirname, '../public/og');

async function convertSvgToPng() {
    const files = fs.readdirSync(ogDir).filter(f => f.endsWith('.svg'));
    
    for (const file of files) {
        const svgPath = path.join(ogDir, file);
        const pngPath = path.join(ogDir, file.replace('.svg', '.png'));
        
        await sharp(svgPath)
            .resize(1200, 630)
            .png()
            .toFile(pngPath);
        
        console.log(`Converted: ${file} → ${file.replace('.svg', '.png')}`);
    }
}

convertSvgToPng().catch(console.error);

Part 4: AWS Deployment

# Architecture

User Browser
    │
    ▼
Route 53 (DNS: yourdomain.com)
    │
    ▼
CloudFront (CDN + HTTPS + WAF)
    │  Origin Access Control (OAC)
    ▼
S3 Bucket (private, stores files)

Step 1: Build the Site

Terminal
npm run build

# This runs:
# 1. generate-og-images.js  → Creates SVGs in public/og/
# 2. convert-svg-to-png.js  → Converts SVGs to PNGs
# 3. next build             → Exports static HTML to /out directory

The /out directory now contains everything: HTML pages, CSS, JS bundles, images, OG images, robots.txt, and sitemap.xml.

Step 2: Create S3 Bucket

S3 (Simple Storage Service) stores your website files. Create a private bucket — CloudFront will handle public access.

SettingValue
Bucket nameyour-website-bucket
Regionus-east-1 (recommended)
Block Public AccessON — block all
Static website hostingDisabled
VersioningDisabled

Upload contents, not the folder

Upload the contents of the /out directory to the bucket root. The bucket should contain index.html at the root, not an out/ folder.

Step 3: Create CloudFront Distribution

CloudFront serves your site globally with HTTPS. It connects to S3 using Origin Access Control (OAC), keeping your bucket private.

SettingValue
Originyour-bucket.s3.us-east-1.amazonaws.com
Origin accessOrigin Access Control (OAC)
Default root objectindex.html
Viewer protocolRedirect HTTP to HTTPS
Alternate domainsyourdomain.com, www.yourdomain.com
SSL certificateACM certificate (us-east-1)

Critical: Default root object

Set Default root object to index.html. Without this, visiting your domain returns an error instead of the homepage.

Step 4: CloudFront Function (URL Rewrite)

S3 with OAC doesn't automatically serve index.html for directory paths. A CloudFront Function rewrites URLs before they reach S3.

CloudFront Function: url-rewrite-index
function handler(event) {
    var request = event.request;
    var uri = request.uri;

    // Add index.html to directory paths
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    } else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}

// Examples:
// /                    → /index.html
// /about/              → /about/index.html
// /articles/my-post/   → /articles/my-post/index.html
// /style.css           → /style.css (unchanged)

Associate this function with the distribution's default behavior as a Viewer request function.

Step 5: Configure Error Pages

S3 returns 403 (not 404) for missing files when using OAC. Configure CloudFront error responses:

Error CodeResponse PathResponse CodeWhy
403/index.html200Allows client-side routing
404/404.html404Shows custom 404 page

Step 6: Configure Route 53 DNS

Create A records pointing your domain to CloudFront:

Record NameTypeAlias Target
yourdomain.comAd123abc.cloudfront.net
www.yourdomain.comAd123abc.cloudfront.net

Part 5: Updating the Site

To deploy changes after editing your code:

Terminal
# 1. Build the site
npm run build

# 2. Upload to S3 (using AWS CLI)
aws s3 sync out/ s3://your-bucket-name --delete

# 3. Invalidate CloudFront cache
aws cloudfront create-invalidation \
    --distribution-id YOUR_DIST_ID \
    --paths "/*"

Cache invalidation is critical

Without invalidation, CloudFront serves cached files until they expire (default: 24 hours). The /* path invalidates everything. First 1,000 invalidations per month are free.

Quick Reference

Key Concepts

  • HTTP: Unencrypted web protocol (port 80)
  • HTTPS: Encrypted web protocol (port 443)
  • TLS: Encryption layer for HTTPS
  • DNS: Translates domains to IP addresses
  • CDN: Distributes content globally

AWS Services

  • S3: File storage (keep private)
  • CloudFront: CDN + HTTPS + caching
  • Route 53: DNS management
  • ACM: Free TLS certificates
  • OAC: Secure S3 to CloudFront connection

Monthly Cost

$0/month for typical traffic on CloudFront free tier. S3 storage cost is negligible (<$0.01/month for ~5MB).