How billybunn.io is built
How this site is built and run: infrastructure as code, automated deploys, performance budgets, and security headers.
TL;DR
- What it is: A personal site built and run like a real system — infrastructure as code, automated deploys, security headers, performance budgets.
- My role: Solo. I own infrastructure, CI/CD, DNS, TLS, font loading, CSS pipeline, content, and deploy.
- Constraints: One person, no budget, no backend. Everything runs on static files and managed AWS services.
- Results:
- ~2 minutes from push to live (GitHub Actions → S3 → CloudFront invalidation)
- Zero infrastructure drift — all resources defined in CloudFormation
- Page weight target: < 50 KB (not yet formally measured; Lighthouse CI is planned)
Context
Most personal sites start from a template and stay there. I wanted to treat mine as a real system — one where I make deliberate infrastructure, performance, and security decisions and document why.
This site is both a portfolio and a working example of how I think about building for the web: constraints first, then tradeoffs, then implementation.
Goals
- Ship a multi-page static site with clean URLs, custom typography, and a warm visual identity.
- Define all infrastructure in code. No manual console changes.
- Automate deploys end-to-end: push to
master→ build → deploy → invalidate cache. - Treat performance and accessibility as constraints, not afterthoughts.
- Keep the system simple enough for one person to operate and extend.
Constraints
- One person. No team, no code review, no on-call rotation.
- No budget. AWS free tier plus minimal S3/CloudFront costs. No paid CI, no paid monitoring.
- No backend. Static files only. No application server, no database, no client-side framework.
- Custom typography. The site uses Figure from Fort Foundry, which means managing font loading performance rather than falling back to system fonts.
Approach
Architecture
The site is a static Eleventy build deployed to AWS.
How it works:
- Push to
mastertriggers GitHub Actions - Actions builds the 11ty site and deploys the CloudFormation stack
- Built files sync to S3 with per-file-type cache headers
- CloudFront serves content over HTTP/2+3 with security headers
- A CloudFront Function rewrites clean URLs (
/about→/about/index.html) - CloudFront cache is invalidated on every deploy
All infrastructure is defined in a single template.yaml — S3 buckets, CloudFront distributions, Route 53 records, ACM certificate, security headers policy, and the URL rewrite function. No manual console changes.
Performance
- Minimal client-side JS. The only JavaScript is a ~30-line FOFT font loader with
sessionStoragecaching. Repeat visits skip straight to the full font. - CSS is inlined. All styles are inlined in
<style>tags — no render-blocking external stylesheets. PurgeCSS removes unused rules per page at build time. - Font loading: FOFT (FOUT with Two Stage Render). A ~5 KB subset font (ASCII only) is preloaded and renders immediately. The full family (Regular, Bold, Italic, Bold Italic) loads in the background. Faux-bold bridges the gap.
- Fluid type and spacing. Utopia
clamp()values handle responsive sizing without breakpoints or JS. - HTTP/2+3. CloudFront is configured for
http2and3, enabling QUIC for supporting clients. - Cache strategy:
- HTML:
Cache-Control: no-cache— browser always revalidates, gets 304 if unchanged - Fonts:
Cache-Control: public, max-age=31536000, immutable— cached for 1 year at both edge and browser
- HTML:
- CloudFront invalidation is scoped. Only HTML and
robots.txtare invalidated on deploy. Fonts stay cached at edge.
Accessibility
- Semantic HTML throughout. Headings follow a strict hierarchy. Landmarks (
<main>,<footer>,<article>,<nav>) are used correctly. - Visible focus styles. All interactive elements have a 2px solid outline on
:focus-visible, withoutline-offsetfor breathing room. - Reduced motion. Link hover transitions are wrapped in
prefers-reduced-motion: no-preference. No animations play for users who prefer reduced motion. - Native semantics over ARIA. The markup is simple enough that native elements handle accessibility. The architecture SVG diagram uses
role="img"with a descriptivearia-label. - Color contrast. The warm-on-cool palette (cream text on dark green-tinted background) was chosen for comfortable reading. Heading and body text colors were tested for WCAG AA contrast ratios.
Security
- HTTPS everywhere. Viewer protocol policy is
redirect-to-https. HSTS is set withmax-age=63072000(2 years),includeSubdomains, andpreload. - Security headers (via CloudFront Response Headers Policy):
X-Content-Type-Options: nosniffX-Frame-Options: DENYX-XSS-Protection: 1; mode=blockReferrer-Policy: strict-origin-when-cross-origin
- S3 is private. The bucket blocks all public access. CloudFront accesses it via Origin Access Identity (OAI). No direct S3 URLs are exposed.
- TLS 1.2 minimum. Certificate enforces
TLSv1.2_2021. - No tracking, no analytics, no cookies. The site collects nothing.
sessionStorageis used only for font-loading optimization and is never sent to a server.
Tradeoffs and decisions
Every system has tradeoffs. Here are the ones I made and why.
- Static site over SPA. No client-side routing, no hydration, no bundle. The tradeoff: no dynamic content without a rebuild. For a portfolio site, that's the right call.
- CloudFormation over CDK. CDK adds a build step, a dependency on
aws-cdk-lib, and synthesized templates that are hard to read. Plain CloudFormation is verbose but predictable. For a single stack, the overhead of CDK isn't justified. (The old CDK stack is archived for reference.) - OAI over OAC. The S3 origin still uses the legacy Origin Access Identity instead of the newer Origin Access Control. OAC is the recommended path forward but requires a migration. This is deferred work — it functions correctly but isn't the modern approach.
- Inline CSS over external stylesheet. Inlining eliminates a render-blocking request but means CSS can't be cached separately. For a site this small, the tradeoff favors inlining. PurgeCSS keeps the inline payload tight.
- Custom font over system fonts. System fonts are faster (zero network cost), but Figure is part of the site's identity. FOFT loading minimizes the performance penalty — the subset is ~5 KB and preloaded.
- No CSP yet. A Content Security Policy would tighten the security posture, but it requires auditing the inline font-loading script and styles. It's next on the roadmap.
Outcomes
- Deploy pipeline works end-to-end. Push to
masterbuilds, deploys infrastructure, syncs files, sets cache headers, and invalidates the CDN — no manual steps. - Zero infrastructure drift. Every AWS resource is defined in
template.yaml. Nothing was created in the console. - Security headers are in place. HSTS, X-Frame-Options, nosniff, XSS protection, and referrer policy are all set via CloudFront response headers policy.
- Font loading is optimized. The FOFT strategy renders a ~5 KB subset immediately and loads the full family in the background. Repeat visitors get the full font from
sessionStoragewith no network cost.
I haven't yet set up formal performance measurement (Lighthouse CI is planned). I'd want to track first contentful paint, total page weight, and cumulative layout shift over time.
What I'd change next
- Add Lighthouse CI to the pipeline. Automated performance and accessibility regression testing on every push. This would give me the FCP and page weight numbers the TL;DR is missing.
- Add a Content Security Policy. Nonce-based CSP for the inline font-loading script,
style-src 'self'for CSS. - Migrate OAI to OAC. Origin Access Control is the modern approach and supports additional features (KMS, PUT requests).
- Structured data. Add JSON-LD for person/website schema.
- OG images. Generate or create a social sharing image for each page.