How I Built This.md
The decisions behind this portfolio site. What I chose, why, and what I deliberately left out.
Three constraints decided almost everything about this site, and I set them before writing any code.
It has to load immediately, because a visitor deciding whether to keep reading will not wait. One person has to be able to maintain it, because that person is me and I have a job. And it has to survive being read by engineers, which rules out anything I cannot explain.
Each decision below follows from those, and each one had an alternative I rejected for a reason I can still give you. That is the part worth reading. The stack itself is unremarkable.
There is also a live AI search on this site, answering from a corpus of my own work, with its own guardrails and evaluation harness. That is a running service, not a widget, and keeping it from turning the whole site into a framework project was the hardest constraint to hold.
The design system
I chose IBM's Carbon Design System, Gray-100 dark theme because it gave me a complete set of rules: spacing scale, type scale, color tokens, grid. That removed hundreds of small decisions I didn't want to make.
The trade-off is that it's austere. Seven colors, two typefaces (IBM Plex Sans and Plex Mono), no gradients, no shadows. The visual identity comes from typography weight and spacing rhythm, not decoration. The one override is the accent color: Carbon's default interactive blue replaced with #50FFB4, a mint green carried over from my previous portfolio. It cascades through every interactive state, link, and label. One color, one job: signal what's active or important.
The framework
This is a content site. Most of it is static text and layout. The question was: how much JavaScript does that actually require?
Astro's answer is zero by default. Every .astro component compiles to HTML at build time. The framework removes itself from the output. Interactive components opt in explicitly with client:load, creating what Astro calls "islands": isolated React components that hydrate independently. That table in the source comment above is the full list. Three React islands. Everything else was finished rendering before the browser's JavaScript engine initialized.
I could have built this in Next.js or Remix. I've used both. But they ship a client runtime whether you need it or not. For a site where 90% of the content is static prose, that felt like paying a tax for convenience I wasn't using. Astro let me keep the component model (composability, props, slots) without the runtime cost.
The content layer
Every piece of long-form writing on this site is an artifact. They live as .mdx files in a content collection with typed frontmatter validated at build time by a Zod schema. If a field is wrong, the build fails. The system supports two rendering paths: standard MDX for prose, and custom React components for interactive content. The Code Samples artifact renders side-by-side code comparisons with inline annotations. Banh Mi Ops has an animated pipeline diagram. Both paths flow through the same ArtifactBody.astro wrapper.
Featured artifacts also render in an off-canvas side panel on the home page. One shared component, two contexts, scoped CSS adjustments for the narrower panel column.
The build pipeline catches a few other things. A custom Astro plugin strips font preload links because Astro's default preloading conflicts with @fontsource. A post-build script verifies the Pagefind search index generated correctly. And the glossary system (inline definition tooltips you'll find on some artifacts) has the strictest check: every definition lives in a single TypeScript registry, referenced by slug. If the slug doesn't exist, the build fails.
Other decisions
A few choices that don't fit neatly into the sections above but shaped the site just as much.
One thing I did for my own amusement: the home page opens with a note in the <body> tag.
Good morning. Or afternoon. Or evening. This is static HTML so I genuinely don't know what time it is for you. But hi. Abe here. You're inspecting a personal site. I respect that. Let me walk you through what's under the hood.
The comment continues for about 60 lines. It explains the framework, the architecture, why CSS animations run the hero instead of JavaScript, and ends with a table:
┌─────────────────┬────────┬────────────────────────┐ │ Component │ Type │ Why it needs JS │ ├─────────────────┼────────┼────────────────────────┤ │ ResumeModal │ React │ open/close + animation │ │ SkillsSpotlight │ React │ SSE streaming + state │ │ SkillsTrigger │ React │ keyboard shortcuts │ ├─────────────────┼────────┼────────────────────────┤ │ ArtifactPanel │ .astro │ CSS + DOM only │ │ SelectionTooltip │ .astro │ CSS + DOM only │ │ ContactModal │ .astro │ CSS + DOM only │ └─────────────────┴────────┴────────────────────────┘ 3 islands. That's the entire JS footprint. The rest was finished before your browser woke up.
As you scroll through the markup, each section has its own note:
And the console has an API:
> abe.ai("your question") ask me anything, streamed to console
> abe.search() open the AI spotlight (or hit ⌘K)
> abe.contact() open the contact modalNone of that is load-bearing. It is there because the people most likely to open the source are the people I would want to talk to.
That's how it's built
I built this with Claude Code, the same tool I wrote Banh Mi Ops to orchestrate, using the methodology described in the other artifacts here. The site is the working example of the practice rather than a description of it.
The test I hold this to is whether every choice on the page has a reason I would defend in review, and a rejected alternative I can name. Three React islands rather than a React site. A design system rather than a visual identity I invented. A file-based database because there is one user. None of those are clever. They are just decisions someone made on purpose, which is the whole standard.
STACK