My portfolio had quietly fallen three years behind. It still worked, but the stack underneath it — Next.js 12, React 17, Tailwind 3, a hosted CMS — was old enough that every small change meant fighting the framework. So I rebuilt it from the ground up. Here's where it started and where it landed:


Why rebuild at all
A quick audit turned up the usual debt of a site you stop touching:
- The stack was ~3 years behind — Next 12, React 17, TypeScript 4.6, an old Babel setup that disabled the faster compiler.
- SEO gaps — no meta description, sitemap, robots, structured data, or even a
langattribute. - Accessibility gaps — a
<div>acting as a hamburger button, no focus states. - A "logo dump" — a flat grid of 20-odd tech logos that said very little.
None of it was on fire. All of it was the kind of thing that makes you avoid the codebase — which is the real cost.
The new stack
The goal was a modern baseline I'd actually enjoy extending:
- Next.js 16 (App Router) + React 19
- Tailwind v4 with a CSS-first design system (
@themetokens, no config file) - Motion for animation, pnpm for packages, TypeScript 6
Moving to the App Router paid for itself immediately: the Metadata API replaced
every hand-rolled <head> tag, and sitemap.ts / robots.ts / dynamic OG
images became a few lines each.
A point of view, not a template
Instead of another centered-cards layout, I gave it a deliberate
technical/terminal aesthetic — an acid-yellow accent on warm near-black,
monospace UI chrome, gridded boxes, a $ whoami terminal, and a scroll-driven
"how I work" flow. Opinionated on purpose: a portfolio should look like the person.
Content went local
The biggest architectural change was dropping the hosted CMS for local MDX + JSON. No external service, no API token, faster builds, and content that lives in git next to the code. The whole data layer collapsed into a thin wrapper over the filesystem:
// lib/data.ts
export const getPosts = async (): Promise<Post[]> => getPostsLocal()
export const getPortfolioBySlug = (slug: string) => getWorkBySlug(slug)The pages never knew the difference — same function signatures, different source.
Case studies and posts (like this one) are now just .mdx files with frontmatter,
which means I can drop a code block or an image into a post without wiring up a
rich-text editor.
Worth it?
Completely. The site is faster, scores better, and — most importantly — it's now something I want to open and tinker with. That's the whole point of a personal site: it should be the easiest place to ship an idea.