Starting with AWS
My initial plan was to serve the static site from S3 behind CloudFront, with KMS-encrypted content using bucket keys to keep costs down. However, I ran into some issues with KMS decryption charges, especially when bucket keys weren’t being used correctly due to policy limitations.
After a quick Reddit conversation and some trial and error, it does look possible to make CloudFront + S3 + bucket keys work without incurring those extra charges - but it added friction that didn’t align with the simplicity I wanted from this project.
When I need a CDN integrated into AWS services, I'll revisit this decision and share the infrastructure templates I create - if anyone needs this pattern as a priority, just let me know via a message on the GitHub repository
Using Cloudflare
While exploring alternatives, I gave Cloudflare a go. Having seen them across the internet for years, I will admit I did not know the bredth of their platform and it didn't take me long to find the capabilities I needed and was very surprised that everything fit into the free plan.
It hit all the key objectives:
- Simple to configure
- The free tier was more than enough
- Fast, global CDN with great UX
- Helpful for learning a new platform beyond AWS
What I Did
- Here’s what the migration process looked like:
- Moved DNS to Cloudflare from Squarespace (which I was migrated to when Google Domains shut down, and a service I'm going to miss)
- Updated DNS records following Cloudflare’s DNS migration guide
- Created a new Pages project for static site hosting and asset storage
- Manually uploaded the static build output from Next.js as a test
- Attached my custom domain to the Pages deployment
Automating Deployment
Once I saw it working, I quickly wrapped the build and deploy process in a simple bash script as this site is statically rendered it’s easy to:
- Update content in PayloadCMS
- Rebuild the site using Next.js
- Push the new build to Cloudflare using Wrangler
Bash build script
1#!/bin/bash2set -e34# Clean up previous build artifacts in one go5rm -rf ./.next ./out ./websiteBuild.zip 2>/dev/null || true67# Run build8npm run build910# Create zip from output directory as we store this in git just in case11(cd ./out && zip -r ../websiteBuild.zip . -q)1213# Deploy only if --upload flag is provided14if [[ "$*" == *"--upload"* ]]; then15 npx wrangler pages deploy16fi
Wrangler configuration file
1name = "name-of-my-pages-project"2pages_build_output_dir = "./out"3compatibility_date = "2025-05-11"45[env]6production = { }
Serving Images from Cloudflare R2
You might notice images on this site load quickly as they’re served from Cloudflare R2 using the PayloadCMS cloud storage plugin. Because R2 exposes the S3 API, setup was easy:
- Installed the PayloadCMS storage adaptor plugin for S3
- Pointed it at my R2 bucket
Why I Like This Setup
- Fast: Static site served from a CDN with global edge locations
- Simple: No backend infrastructure to manage even for deployments
- Cheap: Everything so far is covered by Cloudflare’s free tier
- Flexible: I can still self-host Payload if I need dynamic content later