Stack

How this site is built, tested and deployed, and what the Terraform actually declares.

_

Architecture

Architecture diagram: Route 53 and CloudFront serving a static Next.js site from S3, with CodePipeline and CodeBuild handling deployments and a Lambda + DynamoDB visitor counter behind API Gateway
# Request path
Route 53 resolves travispollard.com and www.travispollard.com to the CloudFront distribution via alias records
CloudFront terminates TLS with an ACM certificate (SNI, TLS 1.2 minimum) and redirects any HTTP request to HTTPS
Cache misses on the default behavior fall through to the S3 static website origin holding the exported Next.js build
Requests under /cfb/data/* go to a second origin instead: the football pipeline bucket, reached through an Origin Access Control rather than a public website endpoint
The visitor counter calls API Gateway, which invokes a Python Lambda that increments a DynamoDB item and returns the count
# Commit to production
1. A pull request runs the GitHub Actions gate: typecheck, lint, a full static export, and the Playwright suite on Chromium and Firefox
2. That job pins TZ=UTC and the same Node version CodeBuild uses, because a gate running a different environment from the deploy has a gap in exactly the shape of the bug it is meant to catch
3. A merge to main fires a webhook that starts CodePipeline within seconds
4. CodeBuild installs with npm ci, builds the static export into frontend/out, and runs the same Playwright suite again
5. The build artifact is deployed to the S3 bucket that backs the CloudFront distribution
6. A final stage invalidates the distribution, so the change is visible without waiting out a TTL

Infrastructure

ResourceConfiguration
S3Static website hosting for the exported Next.js build, read by CloudFront as a custom origin
CloudFrontGlobal CDN, TLS 1.2_2021 minimum, HTTP to HTTPS redirect, compression, PriceClass_100, plus a second origin for /cfb/data/*
Route 53Hosted zone with alias, MX, NS, and SOA records, plus ACM validation records
ACMTLS certificate for the apex and www names, DNS validated through Route 53, in us-east-1 because CloudFront requires it
API Gateway + Lambda + DynamoDBVisitor counter written in Python with boto3
GitHub ActionsPre-merge gate: typecheck, lint, static export, and Playwright on two browsers, pinned to UTC
CodePipeline + CodeBuildPost-merge deploy: build and test per buildspec.yml, S3 deploy, then a CloudFront invalidation
SSM Parameter StoreThe seam between this stack and the football pipeline: distribution id and ARN, so neither reads the other Terraform state
TerraformS3, CloudFront, Route 53 and ACM are the modules s3, cloudfront, route53 and acm; the SSM parameters sit beside them. The visitor counter and the CodePipeline were built outside Terraform

Writeup

I wrote about building this stack end to end, from an empty S3 bucket to a working CI/CD pipeline: From S3 to CI/CD: My Cloud Resume Challenge Journey

Source

The Terraform configuration and the Next.js frontend for this site live in one repository: github.com/jtravisp/travispollard.com