|$ curl https://forge-ai.dev/api/markdown?path=docs/deploy/static
$cat docs/static-hosting-(s3/cloudflare).md
updated Recently·30 min read·published

Static Hosting (S3/Cloudflare)

DeploymentStaticIntermediate
Introduction

Static hosting serves pre-built HTML, CSS, and JavaScript files without server-side processing. It offers the best performance, scalability, and cost efficiency for content that does not require server-side rendering on every request.

Popular static hosting solutions include AWS S3 with CloudFront, Cloudflare Pages, Google Cloud Storage, and Azure Static Web Apps. This guide focuses on the two most common: S3 + CloudFront and Cloudflare Pages.

AWS S3 + CloudFront Setup

S3 provides durable, low-cost storage for static assets. CloudFront adds a global CDN with HTTPS, custom domains, and cache behavior controls.

s3-cloudfront-setup.sh
Bash
1# Create S3 bucket for static hosting
2aws s3 mb s3://my-app.example.com --region us-east-1
3
4# Enable static website hosting
5aws s3 website s3://my-app.example.com \
6 --index-document index.html \
7 --error-document index.html
8
9# Block public access (use OAI/CloudFront, not public bucket)
10aws s3api put-public-access-block \
11 --bucket my-app.example.com \
12 --public-access-block-configuration \
13 "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
14
15# Create CloudFront Origin Access Identity (OAI)
16aws cloudfront create-cloud-front-origin-access-identity \
17 --cloud-front-origin-access-identity-config \
18 "CallerReference=oai-my-app,Comment=OAI for my-app"
19
20# Deploy static files
21npm run build
22aws s3 sync dist/ s3://my-app.example.com --delete
23
24# Invalidate CloudFront cache after deploy
25aws cloudfront create-invalidation \
26 --distribution-id DISTRIBUTION_ID \
27 --paths "/*"

S3 bucket policy for CloudFront access:

s3-bucket-policy.json
JSON
1{
2 "Version": "2012-10-17",
3 "Statement": [
4 {
5 "Effect": "Allow",
6 "Principal": {
7 "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E1A2B3C4D5E6F7"
8 },
9 "Action": "s3:GetObject",
10 "Resource": "arn:aws:s3:::my-app.example.com/*"
11 }
12 ]
13}

danger

Never make your S3 bucket publicly accessible. Always use CloudFront Origin Access Identity (OAI) or Origin Access Control (OAC) to restrict access to CloudFront only. Public buckets are a common source of data breaches.
Cloudflare Pages

Cloudflare Pages offers a simpler, integrated static hosting solution with automatic Git deployments, global CDN, and built-in preview URLs — similar to Vercel and Netlify but on Cloudflare's global network.

cloudflare-pages.sh
Bash
1# Install Wrangler CLI
2npm install -g wrangler
3
4# Login to Cloudflare
5wrangler login
6
7# Create a new Pages project
8wrangler pages project create my-app \
9 --production-branch main
10
11# Deploy to Pages
12wrangler pages deploy dist/ \
13 --project-name my-app \
14 --branch main
15
16# Deploy preview (non-production)
17wrangler pages deploy dist/ \
18 --project-name my-app \
19 --branch feat-new-ui
20
21# List deployments
22wrangler pages deployment list --project-name my-app
23
24# Configure custom domain
25wrangler pages domain set my-app example.com

Cloudflare Pages configuration via _headers and _redirects files (same syntax as Netlify):

_headers
TEXT
1# _headers — Security & cache headers
2/*
3 X-Frame-Options: DENY
4 X-Content-Type-Options: nosniff
5 Referrer-Policy: strict-origin-when-cross-origin
6 Permissions-Policy: geolocation=()
7 Strict-Transport-Security: max-age=31536000; includeSubDomains
8
9/assets/*
10 Cache-Control: public, max-age=31536000, immutable
11
12# _redirects — Route configuration
13/api/* https://api.example.com/:splat 200
14/blog/:slug /article/:slug 301
15/* /index.html 200
CDN Caching Strategies

Proper caching is critical for static hosting performance. The goal is to cache immutable assets indefinitely (fingerprinted files) while ensuring HTML and API responses are always fresh.

Asset TypeCache TTLCache-Control
HTML pagesNo cacheno-cache, must-revalidate
JavaScript (fingerprinted)1 yearpublic, max-age=31536000, immutable
CSS (fingerprinted)1 yearpublic, max-age=31536000, immutable
Images (processed)1 monthpublic, max-age=2592000, immutable
Fonts1 yearpublic, max-age=31536000, immutable
API responsesVariespublic, max-age=60, s-maxage=300

best practice

Use content hashing in filenames (e.g., main.a1b2c3.js) to enable aggressive caching. When the file changes, the hash changes, creating a new URL that bypasses the cache. This is done automatically by Next.js, Vite, and Webpack.
Cache Invalidation

When you deploy new HTML files, you must invalidate the CDN cache to ensure users receive the latest version. Invalidation removes cached content from edge locations.

cache-invalidation.sh
Bash
1# CloudFront: Create invalidation (costs per path)
2aws cloudfront create-invalidation \
3 --distribution-id E1A2B3C4D5E6F7 \
4 --paths "/index.html" "/about/*" "/api/*"
5
6# Invalidate everything (use sparingly — costs more)
7aws cloudfront create-invalidation \
8 --distribution-id E1A2B3C4D5E6F7 \
9 --paths "/*"
10
11# Cloudflare: Purge cache via API
12curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" \
13 -H "Authorization: Bearer $CF_API_TOKEN" \
14 -H "Content-Type: application/json" \
15 -d '{"files": ["https://example.com/", "https://example.com/about"]}'
16
17# Purge everything
18curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" \
19 -H "Authorization: Bearer $CF_API_TOKEN" \
20 -H "Content-Type: application/json" \
21 -d '{"purge_everything": true}'
🔥

pro tip

Minimize cache invalidation costs by structuring your deployment so that only HTML files change on each deploy. Fingerprinted assets (JS, CSS, images with content hashes) never need invalidation — they are effectively immutable and cacheable forever.
Infrastructure as Code (Terraform)

Managing cloud resources manually is error-prone. Infrastructure as Code tools like Terraform and Pulumi define your infrastructure in version-controlled configuration files.

main.tf
HCL
1# Terraform: S3 + CloudFront static hosting
2provider "aws" {
3 region = "us-east-1"
4}
5
6resource "aws_s3_bucket" "static" {
7 bucket = "my-app.example.com"
8}
9
10resource "aws_s3_bucket_website_configuration" "static" {
11 bucket = aws_s3_bucket.static.id
12
13 index_document {
14 suffix = "index.html"
15 }
16
17 error_document {
18 key = "index.html"
19 }
20}
21
22resource "aws_cloudfront_distribution" "cdn" {
23 enabled = true
24 default_root_object = "index.html"
25 price_class = "PriceClass_100" # US + Europe only
26
27 origin {
28 domain_name = aws_s3_bucket_website_configuration.static.website_endpoint
29 origin_id = "S3-${aws_s3_bucket.static.bucket}"
30
31 custom_origin_config {
32 http_port = 80
33 https_port = 443
34 origin_protocol_policy = "http-only"
35 }
36 }
37
38 default_cache_behavior {
39 allowed_methods = ["GET", "HEAD"]
40 cached_methods = ["GET", "HEAD"]
41 target_origin_id = "S3-${aws_s3_bucket.static.bucket}"
42
43 forwarded_values {
44 query_string = false
45 cookies {
46 forward = "none"
47 }
48 }
49
50 viewer_protocol_policy = "redirect-to-https"
51 min_ttl = 0
52 default_ttl = 3600
53 max_ttl = 86400
54 }
55
56 restrictions {
57 geo_restriction {
58 restriction_type = "none"
59 }
60 }
61
62 viewer_certificate {
63 cloudfront_default_certificate = true
64 }
65}
Cost Considerations

Static hosting is generally the most cost-effective deployment option. Here is a cost comparison for a typical site serving 100K monthly visits:

ServiceMonthly CostIncludes
Cloudflare Pages (Free)$0500 builds/mo, unlimited bandwidth, 1GB storage
Cloudflare Pages (Pro)$20Unlimited builds, early access, 50GB storage
S3 + CloudFront~$5-15S3 storage + requests + CloudFront data transfer
S3 + CloudFront + WAF~$25-40Adds WAF rules for DDoS/rate limiting protection

info

For personal projects or small business sites, start with Cloudflare Pages (free tier). It includes unlimited bandwidth, which is a major advantage over S3 + CloudFront where data transfer costs can be unpredictable under traffic spikes.
$Blueprint — Engineering Documentation·Section ID: STATIC-01·Revision: 1.0