Building My Portfolio Website
Every developer reaches a point where they need a place to call their own on the internet. For me, that meant building a portfolio that didn't look like every other dark-mode card grid on the internet. Here's how I built it, what I learned, and what I'd do differently.
Starting With a Vibe
Before writing a single line of code, I spent time thinking about feel. Most developer portfolios fall into one of two traps, either they're a bland white resume, or they're trying so hard to be "creative" that they become unreadable.
I wanted something in the middle: playful and distinctive, but still professional enough that a potential employer wouldn't run away. The decision to go dark came early. Dark themes suit developers, and they give accent colours room to breathe.
The colour palette came next, a coral red (#ff5c35), a mint green (#3ddc84), a warm yellow (#ffd166), and a soft purple (#a78bfa). Not your typical blue-and-white agency palette. These four colours ended up doing a lot of the heavy lifting throughout the site.
Why Tailwind v4?
Tailwind v4 moves theme configuration into CSS via @theme instead of tailwind.config.ts. That means custom colours, fonts, and animations live right next to the rest of your styles. It took some adjustment, but it makes the project feel more cohesive.
Typography
Typography does more work on a portfolio site than most people realise. I paired two fonts:
- Fraunces - a variable serif with a lot of personality. Used for headings and display text. It has an optical size axis, so it looks great both at tiny sizes and huge ones.
- DM Mono - a monospace font for body text, labels, and UI chrome. It reinforces the developer identity without leaning too hard into the "hacker aesthetic."
Both are loaded via next/font/google, which inlines the font CSS at build time and eliminates the flash of unstyled text.
The Custom Cursor
One of the details I'm most happy with is the custom cursor. It's a small coral dot that snaps to the mouse instantly, and a larger ring that lags behind using linear interpolation, a lerp, giving it a satisfying, weighted feel.
rx += (mx - rx) * 0.12;
ry += (my - ry) * 0.12;
That one line is the whole trick. Every frame, the ring moves 12% of the remaining distance to the mouse. Small factor = more lag, bigger factor = snappier. It sounds simple, and it is, but the effect is surprisingly delightful.
The Blog
The blog is powered by Firebase Cloud Functions. Posts are fetched server-side using Next.js's fetch with { next: { revalidate: 60 } }, which means the page is regenerated at most once per minute. This gives me the freshness of a dynamic site with most of the performance of a static one.
Posts are stored with a title, slug, excerpt, tags, cover image URL, and HTML content. The slug becomes the URL, and generateMetadata pulls the post title and excerpt for SEO.
What I'd Do Differently
Start with a design system earlier. I ended up with consistent colours and spacing, but it took longer than it should have because I was making decisions in the moment. Defining the @theme tokens at the start would have saved time.
Use a CMS instead of rolling my own. Firebase works, but a proper headless CMS like Sanity or Contentlayer would have given me a nicer writing experience and better tooling for drafts and previews.
Spend more time on mobile earlier. Most of the desktop design translated fine to mobile, but a few things, the custom cursor, the marquee strip, the hero blob sizes, needed rethinking for smaller screens. Testing mobile throughout rather than at the end would have been smoother.
Final Thoughts
Building your own portfolio is one of those projects where the constraints are entirely self-imposed, which makes it both freeing and paralyzing. There's no client telling you what they want, no brief to follow.
What helped me was committing to a direction early and executing it consistently. Every decision, the font pairing, the card hover rotations, the terminal-style about page, came back to the same question: does this feel like me?
I think it does. And that's the point.
