The requirements
- Secure: no public bucket, no long-lived AWS keys anywhere, and strict security headers.
- Safe to change: every change is seen on staging first, and a bad release can be undone quickly.
- Fast: static files served from a CDN, cached as long as possible without serving stale pages.
- Readable by search engines and AI systems: real HTML on every page, not an empty app shell.
Architecture
- The site is a Vue application built with Vite.
- Files live in a private S3 bucket. CloudFront reads it through Origin Access Control, signing every request with SigV4, so the bucket is never public.
- A CloudFront response headers policy adds a Content Security Policy, HSTS,
X-Content-Type-Options, a referrer policy and frame protection to every response. - The infrastructure is Terraform, run in Terraform Cloud, with a separate workspace for staging and production.
How a change reaches production
- A push to the
stagingbranch deploys to staging. A push tomaindeploys to production, after it has been checked on staging. - GitHub Actions signs in to AWS with OIDC: it exchanges a short-lived token for temporary credentials on an IAM role. The workflows use no stored AWS keys. See how to set that up.
- Each build is uploaded to its own folder in the bucket, named after the commit.
- Terraform then points CloudFront at the new folder, and the cache is invalidated. The switch only happens after the upload has finished, so visitors never see a half-uploaded release.
Rollback is one change
The last two builds are kept in the bucket. Rolling back means pointing CloudFront at the previous commit’s folder again. Nothing needs to be rebuilt or re-uploaded.
Caching without stale pages
- JavaScript, CSS and images have a content hash in their file names, so they are cached for a year and marked immutable.
- HTML pages,
robots.txt, the sitemap andllms.txtkeep the same names between releases, so browsers revalidate them on every visit.
A returning visitor always gets the current page, and the page’s assets usually come straight from their cache.
Real HTML for crawlers
A single-page app normally serves the same empty shell at every URL, and many crawlers, including those behind AI assistants, don’t run JavaScript. So after each build, every public page is opened in headless Chrome and saved as finished HTML. Each page has its own title, description, canonical link and structured data, and the site publishes a sitemap and an llms.txt.
What carries over to client work
- Keyless deployments with OIDC work for any pipeline that deploys to AWS.
- Deploying each build to its own location, then switching traffic, makes rollback fast for static sites and containers alike.
- Staging-first, with the same infrastructure code for both environments, means production is never the first place a change runs.
See Faster deployments for how we apply this to application releases.
Next step
Want the same for your site or app?
On a 30-minute call we’ll look at how you deploy today and what it would take to make it keyless and reversible.