the engine room
How this site works
Most portfolios tell you what someone built. This page shows you how I decide: the architecture, six decision records with the tradeoffs I accepted, and one real postmortem. Everything described here is running in production right now — you're inside the case study.
The system, in one screen
┌────────────────────────────────────────────────┐
you, or your agent │ Cloudflare edge · 300+ locations │
────────────────────▶ worker.js — one file, no framework │
│ ├─ canonical host www → apex 301 │
│ ├─ /mcp MCP server · JSON-RPC 2.0 │
│ ├─ /api/guestbook KV, metadata-only reads │
│ ├─ /api/presence ─┐ │
│ ├─ /api/vitals ──┼─▶ PresenceHub (DO) │
│ ├─ /api/status ──┘ sockets·counters·p75s │
│ └─ everything else ◀─ static dist/ (Vite) │
└────────────────────────────────────────────────┘
build time gen-pages.mjs ─▶ 20 project pages + /api/site.json + sitemap
one content source — humans, agents, and Google can't driftNo origin server. No database on the read path. Every request is answered from the edge — static assets, one KV list call, or one Durable Object hop. The rest of this page explains why each of those choices was made on purpose.
Decision records
Written the way I'd write them for a team — context, the call, and what it costs. A decision without a stated price is a guess.
Context. The centerpiece is a 65,536-particle GPGPU simulation that must hold 60fps while the page scrolls. Every millisecond of main-thread work competes with it. The obvious candidates were Next.js or Astro.
Decision. Vite + vanilla TypeScript. Zero runtime framework, no hydration, no virtual DOM — Three.js and GSAP only where they earn their bytes. The initial JS stays small enough that the WebGL chunk loads as a split bundle behind the boot sequence.
The price. None of the ecosystem's freebies. Routing, reveal orchestration, and the content pipeline are mine to maintain — which forced ADR-002 into existence.
Context. Twenty project pages, an MCP server, a sitemap, and JSON-LD all describe the same work. Maintained separately, they drift — and drift between what humans read and what agents read is the embarrassing kind.
Decision. One generator (gen-pages.mjs) emits the human pages, the agent-readable /api/site.json, and the sitemap from a single content literal. Where it must read a second source (the stack in content.ts), the build throws if the parse fails. A loud broken build beats a quiet wrong one.
The price. Content lives in code, so editing needs an editor, not a dashboard. For a one-author site that's a discount, not a cost.
Context. This site runs a live MCP server at /mcp so agents can browse the portfolio. The official SDK is built for long-lived Node processes and brings a dependency tree; the worker is one file measured in kilobytes.
Decision. Implement the stateless streamable-HTTP transport directly: protocol-version negotiation, 202 notification semantics, batch rejection per the 2025-06-18 spec, correct JSON-RPC error codes. About 200 lines. Reading the spec was the point — I wanted to know what the protocol does, not what a library hides. Verified with MCP Inspector plus four adversarial review agents before shipping.
The price. I own spec compliance as MCP evolves. Acceptable: the surface is five tools, and the playground doubles as a regression check.
Context. A public guestbook needs durable writes on a site with no servers and no database. Reads vastly outnumber writes.
Decision. Each entry is one KV key — reverse-timestamp name, the entry itself in the key's metadata — so a single list() call returns the newest 200 with zero per-entry reads. Writes pass a honeypot, a per-IP rate limit, a content guard, and a 200/day global soft cap. KV can't increment atomically, so the cap races — and undercounting a cap fails safe, which is why it's allowed to.
The price. Eventual consistency and a 200-entry window. For a wall of hellos, that's not a compromise — it's a fit.
Context. Presence (“who's here right now”), counters, and visitor-reported Web Vitals need two things KV can't give: atomic writes and push fan-out. Racing last-write-wins counters lose increments; polling can't do live cursors.
Decision. One SQLite-backed Durable Object holds the WebSocket presence room (hibernation API, so idle tabs cost nothing), the atomic counters, and a rolling 500-sample vitals window. State that must not race lives where writes serialize by construction — that's the whole job description of a DO.
The price. A single global instance is a coordination bottleneck at planet scale. This is a portfolio, not a planet; the day that's wrong is a good day.
Context. Every layer above had a simpler, heavier alternative: a server, a Postgres, a status-page vendor, an analytics suite.
Decision. Everything a visitor or agent reads is a static asset or edge memory. Pages from dist/, project data from build-time JSON, the guestbook from one KV list, live numbers from one DO hop. There is no origin to cold-start, scale, or patch on a Sunday.
The price. Anything truly dynamic must argue its way in. The boring default is build-time — and defending a boring default is most of what senior engineering is.
Postmortem: the site that wouldn't boot in Instagram
Impact. Anyone opening farazian.com from Instagram's in-app browser hung on the boot screen at 00 — indefinitely. Every Instagram referral got the worst possible first impression: a site about shipping software, failing to ship its own first paint.
A visitor screenshot: loader frozen at 00, well past the boot animation's 6-second safety net. That detail was the tell — the safety net itself never ran.
Instagram's WKWebView kills an API the bundle touches at module scope. One dead call at import time took the entire module down — including the boot sequence, including the safety net that was supposed to handle exactly this. The escape hatch was welded inside the thing that crashed.
Single point of failure by architecture: every recovery path shipped inside the app bundle whose death was the failure mode being recovered from.
Three independent layers, each assuming the ones above it are dead. ① An inline watchdog in the document <head> — no bundle dependency — that paints any uncaught error onto the boot label (a screenshot becomes a stack trace) and at 8s force-dismisses the loader and drops the js class, revealing the full static site. ② Crash isolation inside the bundle: every subsystem init individually fenced, with typed fallbacks and a render-loop kill-switch, so one hostile API degrades one feature. ③ A pure-CSS @keyframes failsafe that dismisses the loader at ~10s even if script execution itself is disabled.
Headless Chrome, three scenarios: normal boot unchanged; a deliberately-killed bundle shows the complete site with the error message painted on the loader; a zero-JS sandbox is rescued by CSS alone. All three screenshotted before deploy.
What I keep from this. A safety net that lives inside the thing it protects is decoration. An error nobody can see never gets fixed — the loader now displays the crash, so any future report carries its own diagnosis. And the floor of every failure mode should be “static but complete,” never “blank.”
Colophon
Type is Space Grotesk and JetBrains Mono, self-hosted. Particles are a GPGPU ping-pong simulation in Three.js; motion is GSAP + Lenis. Built by Vite as ~24 static pages, served by a single-file Cloudflare Worker with KV and one Durable Object. Live numbers: /status. Agents: /mcp. Every line in the repo — no templates, no page builders.