Self-directed · this site
How this site is built
A static build on S3 behind CloudFront, with pretty URLs and security headers injected at the edge, provisioned entirely in Terraform across two AWS accounts.
- Discipline
- Cloud · IaC
- Accounts
- Two, delegated
- Edge
- Lambda@Edge
- Astro
- AWS S3
- CloudFront
- ACM
- Lambda@Edge
- Route 53
- Terraform
Sheet B
Specification
The shape of it
Static files in S3, served through CloudFront with an ACM certificate, fronted by a Route 53 hosted zone that lives in a separate AWS account from the hosting infrastructure (see note 1). Everything is declared in Terraform.
The site is small enough that none of this is necessary — which is precisely why it is a useful thing to have built. The interesting parts are the joints: cross-account DNS delegation, edge behaviour, and cache semantics.
Pretty URLs at the edge
S3 static hosting serves objects, not routes. A request for /projects/capstone
has no matching object; the object is /projects/capstone/index.html.
A Lambda@Edge function on viewer-request rewrites directory-style and
extensionless paths to their index documents. The same function, on
viewer-response, sets HSTS, X-Content-Type-Options, X-Frame-Options,
referrer policy, and a strict Content-Security-Policy (see note 2).
Caching, and one honest mistake
Assets are served with a one-year immutable cache and HTML with
max-age=0, must-revalidate. That split is standard and correct — except the
resume PDF was an asset that changed. Browsers honour immutable by not
revalidating at all, so an updated resume stayed invisible behind a stale
copy, and the workaround was appending a version query by hand across five
HTML files on every update.
The fix was not a better query string. Importing the PDF through the build
pipeline gives it a content-hashed filename, so a new file is a new URL and
immutable becomes true rather than a claim. The manual cache-bust is gone.
Verifying what ships
The build fails on a CSP violation rather than deferring the discovery to production (see note 3). Fonts are self-hosted and subset to Latin, which removes the last third-party origin from the critical path — and lets the policy drop the font CDN entirely.
Sheet C
Notes
3 items
Sheet D
Revisions
What I would change
- 01Move the Terraform state to S3 with locking. It is local today, which is fine for one operator and wrong for any number greater than one.
- 02Add a stable /resume.pdf alias alongside the content-hashed asset, so a URL pasted into a job application keeps working after the file changes.