Command Palette

Search for a command to run...

Projects / Portfolio Platform / Overview

Portfolio Platform

Next.js App Router, RSC-first rendering, keyless CI/CD to AWS, and a design system single-sourced in code — the presentation layer you're reading right now.

Year
2026
Role
Solo
Focus
Frontend · Infra
Status
This site
Next.jsTypeScriptTailwindAWSTerraformCI/CD

Problem

A portfolio site is easy to template and easy to make forgettable. The goal here was the opposite: something that felt engineered rather than assembled — real infrastructure behind it (this page and the live Market dashboard both run on it), a design system that can't quietly drift from what's actually in the browser, and a deploy pipeline with no long-lived cloud credentials sitting in CI waiting to leak.

Approach

  • Next.js App Router with Server Components as the default, not the exception — pages fetch data server-side and ship minimal client JavaScript. Client components are reserved for genuinely interactive pieces: the command palette, the mobile drawer, the live charts.
  • Design tokens are single-sourced as CSS custom properties, mapped into Tailwind v4's @theme, with the radius scale derived from a single base value. The mockups are a visual reference only — where a mockup and the token layer disagreed, the tokens won.
  • shadcn/ui on Radix-family primitives, themed to those same tokens, so focus trapping, keyboard navigation and ARIA wiring come from the primitives rather than hand-rolled logic.
  • /projects and /blog run on Content Collections + MDX: Zod-validated structured frontmatter plus a free-form MDX body, so publishing a case study or post is a new file rather than a schema migration or a CMS deploy.
  • Keyless CI/CD: GitHub Actions assumes an AWS IAM role over OIDC on every push, builds an arm64 image to match the Graviton host, pushes to ECR, and triggers a deploy over SSM. No static AWS access keys exist in either repository's secrets.
  • One EC2 box runs the whole stack behind Caddy via Docker Compose. That's sized to the traffic a personal site actually sees, and it buys a specific set of costs — deploys that replace containers in place, one failure domain, and services sharing memory with nothing enforcing a boundary between them.

Results

  • Everything you're looking at — Home, About, a live streaming Market dashboard (see the Ticker case study), Projects, and Blog — served from one box for a few dollars a month.
  • Every push runs lint, typecheck, build, and unit tests, then contract tests against a real Ticker container and Playwright accessibility checks. A change that fails any of them doesn't ship.
  • Zero long-lived cloud credentials anywhere in either GitHub repository — OIDC-issued short-lived tokens only, scoped per workflow run, with the role's trust policy pinned to the exact repository and branch allowed to assume it.

Lessons

  • GitHub's OIDC subject claim isn't the shape most walkthroughs assume, and the failure is maximally unhelpful: every value in the trust policy reads as correct and the request is still denied. The tempting fix — wildcarding the whole claim — turns a deploy role into one any stranger can assume.
  • An automated accessibility check found a contrast failure inside the design tokens themselves, not in a component. Darkening the brand colour would have degraded the four roles it was already fine for — borders, rings, links, glows — to fix the one it wasn't. Splitting it into a second token by role cost nothing downstream, because nothing had ever referred to the colour by value. Tokens didn't make the site accessible; they made the fix cheap.
  • Static-versus-dynamic rendering is inferred from what a route fetches, not declared. One uncached fetch in shared code silently converts a statically generated page into one rendered per request, with no error and no warning — a thing you only notice if you read the build output deliberately.
  • A personal site is a legitimate place to hold production-grade discipline — leader election, shutdown-flush semantics, honest data-provenance flags. None of that is overkill if the point is demonstrating how the real thing would get built.

Go deeper