Shane Townsend

How this site is built

A small project, but it touches the same pieces as a larger deployment: source control, CI/CD, DNS, TLS, security headers and cost control.

Goal

A fast, secure resume site that costs almost nothing to run and updates automatically when I push a change.

Architecture

Deployment flow for stownsend.com A local edit is pushed to a GitHub repository, GitHub Actions builds and deploys it to Azure Static Web Apps, and visitors reach it through DNS at stownsend.com over HTTPS. Local edit HTML / CSS / JS in Git git push GitHub repository main branch triggers GitHub Actions workflow generated by Azure deploys Azure Static Web Apps Free plan · managed HTTPS certificate CNAME stownsend.com DNS at Namecheap

Decisions

  • Static HTML/CSS/JS — no server to patch and no database, so very little attack surface.
  • Azure Static Web Apps (Free) — $0 hosting, managed TLS, built-in GitHub deployment.
  • GitHub Actions — every push to main deploys automatically.
  • Custom domain — registered at Namecheap and pointed at Azure with DNS records.
  • Security headers — a strict Content-Security-Policy, X-Content-Type-Options, X-Frame-Options and a referrer policy, set in staticwebapp.config.json.
  • No inline scripts or styles — that lets the CSP stay at 'self' without unsafe-inline.
  • Cost control — an Azure budget alert as a safety net, even on a free plan.

What I learned

Check the file, not the filename. My resume download opened as a blank page. The browser console said the PDF had an invalid structure, and inspecting the file showed it was actually a LibreOffice Draw document renamed to .pdf. The fix was exporting a real PDF instead of renaming.

Security headers can break legitimate features. Before finding that, I suspected my X-Frame-Options: DENY header, because browsers' built-in PDF viewers render PDFs in a frame. It wasn't the cause here, but it's a real interaction, so I added a route-specific override for the /assets folder and kept the strict headers everywhere else.

Git has a staging area for a reason. A commit that reported "no changes added to commit" taught me to read git status before every commit. Changes show up red until they're staged and green once they are.

Let the platform write the pipeline, then read it. Azure commits the GitHub Actions workflow to the repo when the app is created, so my local copy was behind until I ran git pull. Reading the generated workflow showed exactly how the deploy token and build settings connect the repo to Azure.

Keep secrets out of the repo. Before the first commit I moved a credentials note out of the project folder and added a .gitignore that blocks files with names like *creds*, .env and *.key.