July 29, 2026

Rebuilding my portfolio for the AI era

My old portfolio was a 2021 artifact: dark theme, a nine second preloader with background audio, WebGL hover effects, and a project list that ended in 2022. It looked like every other developer portfolio of that generation, and it said nothing about what I actually do now, which is building and evaluating LLM-powered products.

So I rebuilt it from scratch. A few decisions turned out to matter more than the rest.

Restraint beats spectacle

When anyone can generate a flashy site in an afternoon, spectacle is cheap. What is expensive, and what actually signals competence, is evidence: real projects, real metrics, real architectural decisions explained plainly. The new design borrows from editorial portfolio sites: light background, two typefaces, one accent color, big screenshots, and case studies that follow a fixed shape of problem, approach, outcomes, stack.

Content as typed data

Every project, role, and line of copy lives in a typed content/ directory. The pages are just renderers. Adding a project is one entry in projects.ts with the compiler checking the shape. Case studies get NDA and visuals-pending flags, because honesty about what I cannot show reads better than pretending it does not exist.

Two bugs worth writing down

A discontinued Node pin can outlive the site that set it. My Vercel project still carried a Node 16 setting from 2021. Every deploy of the new Next.js 16 app died in five seconds, before install, with a one line error buried in the build log. The code was never the problem. If a build fails faster than an install could possibly run, look at the platform settings, not the repository.

useReducedMotion cannot save you at hydration time. My scroll reveals respected reduced motion via the hook, or so I thought. The hook returns its real value only after mount, while the animation library bakes the hidden initial state into the server-rendered HTML and reads initial exactly once. Users with reduced motion enabled got every animation anyway. The fix was to stop trusting JS timing entirely and enforce it in CSS:

@media (prefers-reduced-motion: reduce) {
  .motion-safe-anim {
    opacity: 1 !important;
    transform: none !important;
  }
}

Inline styles lose to !important, so this wins no matter when React wakes up. Accessibility guarantees belong in the layer that cannot race.

What is next

The site now ships with a contact pipeline, structured case studies, and this writing section. The plan is short notes like this one: benchmark design, agent orchestration patterns, and the unglamorous production details that actually decide whether AI products work.

← All writing