Skip to content
All projects
Open source · You're looking at itCase study

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
Next.js 15React 19TypeScriptTailwind CSS 4MotionThree.js / R3F

01The 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.

02Architecture

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.

03Performance 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.

04Accessibility & 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.

05What 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.