Personal platform · 2026

This Site

Live
  • Frontend
  • Design system
  • Content
RoleEverything
DurationOngoing
TeamSolo
StackNext.js, TypeScript, CSS Modules, Zod, Resend

01 — The problemThe portfolio, lab, and teaching archive you are reading. Built to be added to weekly for years, which changed almost every decision.

Most developer portfolios are built once, look sharp for a year, and then rot because adding a post means fighting the codebase. I wanted the opposite trade: slightly more structure up front so that publishing a lesson is editing one typed array.

02 — ConstraintsWhat I had to work inside

  • Adding content must never require touching a component.
  • No client-side JavaScript for anything that can be static.
  • It has to survive me not looking at it for four months.

03 — ApproachTyped content, not a CMS

Content lives in typed TypeScript arrays under `src/data`. That sounds primitive next to a headless CMS, and it buys three real things: the compiler catches a malformed lesson before it deploys, content is versioned with the code that renders it, and there is no external service to go down, expire, or start charging.

Bodies are a discriminated union of block types — text, list, code, note, quote — so the renderer is one exhaustive switch. Adding a block type is a compiler error everywhere it needs handling, which is exactly where you want to find out.

04 — ApproachNo animation library

The previous version pulled in a full animation runtime to fade sections in on scroll. That is now one IntersectionObserver hook and a CSS transition on a data attribute — about thirty lines, zero dependencies, and it runs on the compositor.

ts
// the whole reveal system
const io = new IntersectionObserver(
  (entries) => {
    for (const entry of entries) {
      if (!entry.isIntersecting) continue;
      entry.target.setAttribute('data-shown', 'true');
      io.unobserve(entry.target); // reveal once, then stop paying for it
    }
  },
  { rootMargin: '0px 0px -12% 0px' },
);

05 — DecisionsWhat I chose, and what it cost

CSS Modules over a utility framework

ChoseCSS Modules with custom properties
OverTailwind
BecauseZero runtime, no build plugin, and the token layer is plain CSS variables that any future tool can read.
What it costMore files, and I lose the speed of composing a layout without leaving the JSX.

Dropped the animation library

ChoseIntersectionObserver + CSS
OverFramer Motion
BecauseIt was doing fade-ups. That is not worth a runtime dependency on every page.
What it costShared-element and layout transitions are now off the table without writing them by hand.

06 — OutcomesWhere it landed

0Runtime UI dependenciesReact and Next only.
18Routes
ASelf-audited a11yKeyboard paths and contrast checked per page.

07 — RetrospectiveWhat I would tell myself at the start

  • Optimising for the hundredth post rather than the first changes what "simple" means.
  • A discriminated union for content blocks turns content bugs into compile errors.
  • Most portfolio animation is a dependency you are renting to do something CSS does for free.