← Back to Work
2026Design & Engineering

Portfolio v2 — Next.js

A ground-up rewrite migrating from Create React App to Next.js 16 with React 19, Tailwind 4, TypeScript, and a full dark/light mode system.

Next.js · React 19 · TypeScript · Tailwind 4


The original portfolio was built on Create React App — a tool that is now unmaintained and no longer the right foundation for a production site. This rewrite had clear goals: static generation, proper routing, a scalable theming system, and zero runtime bloat.

The Problem

CRA produces a single-page app with no server rendering and no route-level code splitting. Every page — even a simple blog post — ships the entire React bundle. There is also no path to dark mode without introducing a third-party library or accepting a flash of wrong theme on load.

The Approach

Next.js 16 with the App Router solves routing, static generation, and code splitting in one move. Every blog post and project page is pre-rendered at build time via generateStaticParams — no JavaScript required to read them.

Tailwind 4 replaces the CSS Modules approach from v1. The CSS-first configuration model means no tailwind.config.js — design tokens live in an @theme block inside globals.css alongside the runtime CSS custom properties that power dark mode.

:root {
  --color-bg: #ffffff;
  --color-ink: #111111;
}

[data-theme="dark"] {
  --color-bg: #0a0a0a;
  --color-ink: #ededed;
}

The anti-flash pattern is a synchronous inline <script> in <head> that sets data-theme before the first paint. No library, no flash, no hydration mismatch.

The Result

The build produces fully static HTML for every page. The theme system has zero runtime cost — it's a CSS attribute selector switching custom properties. The marching-ants animation from v1 is preserved as a @utility class shared across every bordered component.