alexanderwking.com
The site you're reading right now — built end-to-end, shipped on Cloudflare Pages, polished against Lighthouse budgets, secured behind real headers, and tested at the only spot that actually matters: the auth crypto.
Why
I've been an engineer for 12+ years and never had a personal site that felt like me. Most of mine looked like every other dev portfolio: white background, sans-serif, list of jobs, link to GitHub. Fine. Bland. Said nothing about how I think or what I actually care about.
This one is different by design. It's a blend of personal and professional — peers and recruiters find substance, family and friends track life updates, and neither audience feels out of place. Dark theme, electric amber accent, a custom cursor on desktop, a Konami-code easter egg that flips it into Windows 98 colors. Sports posts about the 2003 Nets sit alongside an /uses page about my work observability stack. It works.
Stack
| Layer | Tool |
|---|---|
| Framework | Next.js 16 (App Router) with output: "export" |
| Runtime | React 19 + TypeScript (strict) |
| Styling | Tailwind CSS v4 + CSS variables for the theme |
| Motion | Framer Motion |
| Content pipeline | unified · remark-gfm · rehype-slug · rehype-pretty-code (shiki) |
| Hosting | Cloudflare Pages |
| Edge logic | Cloudflare Pages Functions (for family-gallery auth) |
| CI | GitHub Actions: lint + build + Vitest + Lighthouse CI |
Static export everywhere it can be; edge functions only where the problem actually needs them.
The two non-obvious pieces
Auth without a server
Most of the site is static HTML served from the edge. But there's a
private route — /gallery/family — that needs gating. Static export
can't do auth on its own.
Rather than spin up a backend or move the whole site off static export, I composed two things: a static-export Next.js build + Cloudflare Pages Functions for the auth-only bits. Edge middleware gates the private route by verifying a signed session cookie; a separate edge function issues that cookie after validating a shared password against a server-only secret. No Node server anywhere; the whole site deploys as one bundle.
The composition is what's nice. Static for the 95% case where it's
the right tool, edge functions for the 5% that actually needs runtime
logic. The boundary lives in the filesystem layout (/functions
directory at the repo root vs. everything else in /src), so it's
visible at a glance rather than buried in config.
Per-post Open Graph card pipeline
Every blog post gets its own dynamically-generated 1200×630 OG card via
Next's opengraph-image.tsx convention in the [slug] segment. The
images are pre-rendered at build time using ImageResponse from
next/og, sharing visual language with the site-wide card (amber top
bar, subtle grid, ghost king silhouette in the corner).
The card surfaces post title, tags, date, and the AWK lockup — so when someone shares a blog URL on LinkedIn / iMessage / Slack, it doesn't just say "Alexander W. King" again. The post itself is the headline.
There's a small Content-Type gotcha to be aware of: Next.js emits image
routes without file extensions (literally out/opengraph-image, no
.png), and Cloudflare Pages defaults to application/octet-stream on
extensionless files. Combined with the nosniff header I set globally,
strict consumers like iMessage rich previews skip rendering. Pinning
Content-Type: image/png in public/_headers is the fix.
What I built that I didn't expect to need
- A Python photo-processing script (
scripts/process-photo.py) that takes iPhone HEIC files, auto-rotates them, strips EXIF (including GPS — critical for family photos), resizes to 1200px wide, and emits both JPEG and WebP. Reusable in one command per photo. - A reading progress bar on blog posts. Standard delight; 2px amber bar that fills as you scroll. Pure CSS transform, raf-throttled, ~80 lines.
- A gallery lightbox with keyboard nav. Once I had real photos worth seeing full-size, the grid-tile-only view was wrong.
- Vitest unit tests for the auth crypto. Zero tests was acceptable for the rest of the codebase; for the function deciding "can this person see family photos," not so much. 29 tests, 100% line coverage.
What's intentionally not here
- A CMS. Markdown files in git, full stop. No Contentful, no Sanity, no headless anything. Faster, cheaper, lower maintenance, and writes are git commits which are perfect change history.
- A comment system. Mailto link works. If demand emerges I'd add Giscus (GitHub Discussions-backed), not Disqus.
- A newsletter. Currently a
mailto:placeholder on/blog. Will wire up Buttondown when subscriber demand exists; building newsletter infra for 0 subscribers is engineering theater. - Analytics tracking pixels. Cloudflare Pages auto-injects privacy-friendly Web Analytics at the edge. No cookies, no GA, no consent banner needed.
Workflow
Every change ships through a PR. Direct pushes to main are blocked at the agent layer. GitHub Actions runs lint, build, Vitest, and Lighthouse CI on every PR; Cloudflare auto-builds a preview URL for each branch and comments it on the PR.
Dependabot opens patch + minor bumps grouped by ecosystem (next, react, content-pipeline, tooling); major bumps each get their own PR for review. Patch/minor bumps can auto-merge once CI passes, gated by repo visibility (currently disabled on private/free-tier; will activate when the repo goes public).
The whole thing is reproducible — README has the local dev setup, ROADMAP has the open questions and forward work.
What I'd do differently
- Start with the design system in CSS variables from day one. I did this eventually, but a few early components hard-coded amber hex values that I had to retrofit when retro mode (Konami code) needed to swap the palette. Cost: maybe 20 min of cleanup. Worth remembering for next time.
- Skip the Picsum placeholders. I used Picsum as gallery placeholders while waiting on real photos. Looked fine in dev, signaled "demo" in production. Real photos beat polished placeholders every time — would've shipped the first real ones on day one if I had them processed.
Links
- Live: https://alexanderwking.com
- Source: https://github.com/awk86/awk-personal-website
- Stack details: see
/useson the live site for the full work + personal toolchain