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.

333 KB→293 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 uploadingout/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
@themedesign system — oklch palette, radius and shadow scales, three font roles - 04
/resumerendered 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
Screens
Walk through the build
Next up