Skip to content
All projects
Static exportDesign systemNo-JS render

This Portfolio

This site is its own work sample, so the constraints were the interesting part: it deploys by uploading a folder to cPanel shared hosting, which means no Node runtime, and it needs to paint before the JavaScript lands rather than after. Every route is prerendered to static HTML, all copy lives as typed modules rather than in JSX, and the design system is Tailwind v4 theme tokens — which is what lets the print stylesheet recolor the entire site for paper by redefining tokens instead of overriding components.

2026Solo buildSolo — design and build
This Portfolio cover

333 KB293 KB

First load, gzipped

20

Pages prerendered

0

Server processes

0

Pages needing JS to render

Problem

A developer portfolio gets read as source, not just viewed, so the implementation is part of the argument. It also came with a hard hosting constraint: deployment is a folder upload to cPanel shared hosting, so there is no Node runtime — no API routes, no server actions, no incremental regeneration, no on-demand image optimization. And much of the audience is on mobile networks where the JavaScript bundle arrives late, which means anything that waits for hydration to become visible is effectively invisible.

Approach

Static export, with everything resolved either at build time or in the client. Copy lives as typed modules in src/data, so pages are thin renderers and a content change never touches JSX — adding a project generates its detail route, its sitemap entry, and its cards. The design system is Tailwind v4 @theme tokens as the single source of truth, which is why the print stylesheet can recolor the whole site by redefining tokens rather than fighting components. First paint is protected on purpose: React 19's preload() for the hero image, CSS-only entry animations for the avatar and the page shell so nothing waits on hydration, and a <noscript> fallback so the page still renders with scripting off. The animation layer is CSS end to end, so no animation library sits in the critical path — worth 45 KB gzipped on its own. Lenis still provides inertial wheel scrolling, which CSS genuinely cannot do, but it is dynamically imported so it downloads after hydration rather than blocking it.

Outcome

20 routes prerendered to static HTML, deployable by copying a folder, with no server process in the stack. /resume derives from the same modules as the rest of the site, so the resume cannot drift from it, and browser-print turns that page into the PDF. Every route renders its content without JavaScript.

Highlights

  • 01Static export (output: "export") deployed by uploading out/ to cPanel shared hosting — no Node runtime
  • 02All copy as typed modules in src/data; adding a project generates its route, sitemap entry, and cards
  • 03Tailwind v4 @theme design system — oklch palette, radius and shadow scales, three font roles
  • 04/resume rendered from the same data as the site, print-styled so browser-print produces the PDF
  • 05Print stylesheet recolors the entire site by redefining theme tokens rather than overriding components
  • 06Light and dark themes resolved entirely in CSS — the OS preference is honored with JavaScript disabled, and an explicit choice is replayed before first paint, so neither state ever flashes
  • 07⌘K command palette over every page, project and action, lazy-loaded so its dialog stays out of the first-load bundle
  • 08LCP protected with React 19 preload() and CSS-only entry animations, so first paint never waits on hydration
  • 09Renders without JavaScript — CSS route transitions plus a <noscript> reveal fallback
  • 10Animation layer is pure CSS — aurora gradients, grid patterns, magnetic buttons, marquees, scroll reveals, scroll progress — with scroll-linked values passed to CSS as custom properties so scrolling never re-renders React
  • 11No animation library in the critical path: dropping Motion for CSS keyframes and an IntersectionObserver took 45 KB gzipped off first load, and Lenis smooth scroll is code-split so it loads after hydration — all measured off the built output
  • 12Radix primitives composed shadcn-style: dialog, sheet, tooltip, separator
  • 13Contact form on react-hook-form and Zod, posting via EmailJS since there is no server to post to
  • 14SEO: metadata templates, Person JSON-LD, generated sitemap and robots
  • 15Mobile: hide-on-scroll bottom nav with safe-area insets and 48px touch targets

Stack

  • Next.js
  • React 19
  • TypeScript
  • Tailwind CSS v4
  • Radix UI
  • Zod

Links

Screens

Walk through the build

6 images

Next up

Want to see more work?

View all projects