
Every developer rewrites their portfolio eventually. Here's the stack I chose, what surprised me about Next.js 15's App Router, and a few things I'd do differently.
Every developer eventually reaches the point where they open their portfolio, wince at the design they thought was brilliant three years ago, and decide it's time for a complete rewrite. This was mine.
I'd been using a static site generator for a while — nothing wrong with it — but I wanted something that felt more alive. Something I could ship features to on a weekend and actually enjoy maintaining.
The short answer: the App Router finally clicked for me. When React 18 introduced concurrent features, and Next.js layered the server component model on top, I kept bouncing between "this is brilliant" and "wait, why is my state gone?" until something just... made sense.
A few things that sold me for this specific project:
(portfolio) group for the public site and an admin section with its own layout and auth middleware, all cleanly separated in the same repo.next/image does the heavy lifting so I don't have to think about responsive images.Here's what the project runs on:
Async params in Next.js 15. When they made params and searchParams return Promises, I hit a wall of type errors that took an embarrassingly long time to trace back to a single root cause. Now every dynamic page looks like this:
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
// ...
}
It's fine once you know. But the migration guide really buries that detail.
I was dreading the usual dark mode dance — toggling classes, fighting Tailwind's dark: variants in deeply nested components. Instead I went with CSS custom properties:
:root {
--bg-primary: #f7f5f0;
--text-primary: #111009;
--accent: #c8820a;
}
html.dark {
--bg-primary: #0a0a0a;
--text-primary: #f5f5f5;
--accent: #f0a500;
}
Every component just uses var(--accent) and it works in both modes automatically. No dark:text-yellow-400 sprinkled everywhere.
The best design system is the one you'll actually maintain.
A few honest reflections after shipping this:
The blog itself is new — this is the first post. I plan to write regularly about frontend patterns, performance, and the random rabbit holes that absorb my weekends. If that sounds interesting, check back soon.