This portfolio.
A production-grade Next.js site — every effect hand-rolled, every page statically generated.
This site is its own case study: a Next.js 15 App Router application with a custom cursor engine, a procedural three.js hero, scroll-choreographed reveals and a ⌘K command palette — built to prove that visual ambition and engineering discipline aren't in tension. Everything you're seeing ships as static HTML with the expensive parts lazy-loaded behind first paint.
- Role
- Design + engineering, solo
- Type
- Open source
- Stack
- Next.js 15 · React 19 · Tailwind 4
- Hosting
- Vercel · fully static output
01 — The goal
The goal
A portfolio has one job — get a recruiter from landing to "I want to talk to this person" in under a minute — and one trap: sites that demo animation libraries instead of engineering judgment. The constraint I set was that every effect had to be written from scratch, and none of it could cost the first paint anything.
02 — Architecture
Architecture
The site is a Next.js 15 App Router application that renders to fully static output. All copy lives in one typed content layer (a single data module), so every section, the resume page, metadata and structured data are generated from the same source of truth — change a job title once and the whole site agrees.
Interactive surfaces are client components at the leaves; everything that can be a server component is one. The case-study pages you're reading are server-rendered from typed content with per-page metadata, static params and generated OpenGraph images.
→One typed content layer
Copy, experience, projects and case studies are TypeScript modules. The compiler catches a missing field before a recruiter ever sees a broken card.
→Server by default, client at the leaves
Sections are server components; only the pieces that need the DOM (cursor, palette, 3D, reveals) opt into the client.
→Static everything
Every route pre-renders at build time. There is no runtime data dependency, no cold start, nothing to fail.
03 — Performance decisions
Performance decisions
The three.js particle sphere is the most expensive thing on the site, so it's treated like a liability: dynamically imported with SSR disabled, downloaded only after hydration, and mounted only on desktop viewports. First paint is pure HTML and CSS — the 3D arrives late and silently, or not at all on mobile.
Fonts load through next/font with swap display, so text renders immediately in a fallback and upgrades without layout shift. Animations run on transforms and opacity only — nothing that triggers layout — and the film-grain, aurora and marquee effects are pure CSS, costing zero JavaScript.
04 — Accessibility & UX care
Accessibility & UX care
Motion is a garnish, not a dependency: every reveal honors prefers-reduced-motion and collapses to instant, fully-visible content. The custom cursor only activates on fine-pointer devices, keyboard focus is visibly styled throughout, the command palette is fully keyboard-driven, and a skip link jumps past the chrome. Semantics stay honest — real headings, landmarks, and descriptive labels on every icon-only control.
05 — What building it taught me
What building it taught me
Hand-rolling effects you could import is slower — and worth it exactly once, here, where the site is the demo. The durable lessons were about budgets: deciding what an effect is allowed to cost before writing it, and letting the answer kill features. A portfolio that scores poorly in Lighthouse while claiming to care about performance is self-refuting; keeping this one static and lazy was the point.
If you only remember four things
- Fully static Next.js 15 output — no runtime dependencies, instant first paint.
- Three.js hero lazy-loaded post-hydration, desktop-only.
- Reduced-motion aware, keyboard navigable, SEO-complete.
- Single typed content layer drives every page, including this one.