← Back to Blog

Building a Portfolio That Doesn't Suck

By Aman RawatFebruary 26, 20268 min read
Next.jsMDXPortfolioDesign

The kind of portfolio that gets read

At one point, this portfolio looked finished. The hero animated nicely, the sections were in place, and the colors felt right. Then I sat down to write the first real case study and realized the problem immediately: the site looked polished, but it did not sound like me yet.

The design was ahead of the writing

The first version shipped fast, but it leaned too hard on polish. Once I started writing longer posts, I could feel the weak spots: headings that looked good but did not guide the eye, sections that sounded broad instead of specific, and details that felt more "portfolio template" than personal site.

What followed was less of a redesign and more of an edit. I kept the parts that felt true to me, cut the parts that felt performative, and rebuilt the site around one idea: if someone spends a few minutes reading, the experience should feel calm, clear, and intentional.


Next.js App Router + MDX (and why I stopped debating it)

I picked Next.js and MDX for two different reasons. Next.js solved the site shell: routing, layouts, and fast page delivery. MDX solved the writing workflow: long-form posts that live in the repo and can still pull in custom visuals when a paragraph alone is not enough.

Pages and layouts: App Router

The UI shell (home, blog index, project pages) runs on the App Router. I like it for portfolio work because it keeps the structure obvious. Routes live where you expect, layouts stay close to the pages they affect, and static generation gives me performance without extra ceremony.

Posts: MDX in the repo

Blog posts are not React pages I hand-wire one by one. They are written in MDX: mostly Markdown, with components only where they genuinely help. Each article is one file in the content folder, committed like code. That matters to me because writing and editing stay simple. If I want to fix a sentence, add a visual, or tighten a section, I do it in the repo and ship it with the rest of the site.


Typography is the navigation

I spent more time on fonts than on most components. The combination of Bricolage Grotesque for headlines, Geist for body copy, and JetBrains Mono for code does something practical: it creates hierarchy you can feel while scanning.

Every tweak was in service of readability: line height for body text, letter spacing for headings, and code styles that support the paragraph instead of hijacking it.

The three voices of the site
Bricolage Grotesque
Headlines that feel a little human
Geist

This is the voice that does most of the work. I wanted body copy that stays quiet, readable, and comfortable over a long scroll.

JetBrains Mono
Used for code, labels, and small UI details: rehype-pretty-code

Color with purpose (not decoration)

I was already using Catppuccin Mocha in VS Code. It feels cozy and comfortable to stare at for long sessions, and that mattered more to me than chasing a trendy dark theme. I brought the same palette to the portfolio because I wanted the site to feel like an extension of my workspace, not a separate brand costume. It says something small but real about my taste and how I like software to feel.

The palette stays warm without turning the site into a theme park. The accent (#fab387) handles emphasis: CTAs, hover states, selected UI, and subtle highlights.

One rule kept everything honest: if something didn't need attention, it didn't get the accent.

Catppuccin Mocha palette (site tokens)

I was already living in Catppuccin all day inside VS Code, so bringing the same palette into the portfolio made the site feel familiar from the first screen. Peach handles emphasis. The darker surfaces do the quiet work.

Base
#1E1E2E
Mantle
#181825
Crust
#11111B
Surface 0
#313244
Overlay 0
#6C7086
Text
#CDD6F4
Subtext 0
#A6ADC8
Accent (Peach)
#FAB387
Mauve
#CBA6F7
Lavender
#B4BEFE
Blue
#89B4FA
Teal
#94E2D5
Green
#A6E3A1
Yellow
#F9E2AF
Red
#F38BA8
Pink
#F5C2E7

Animation as feedback, not spectacle

Every animation on this site has a job:

  • Scroll reveals signal "new section is here."
  • Hover states confirm "yes, this is interactive."
  • The staggered hero entrance gives the page a rhythm.

When animation does that reliably, you stop noticing it. That's the point. The interface starts to feel responsive, not staged.


Technical details that changed performance (for real)

The nicest-looking bug in this project was the hero canvas. It looked great on my machine, then started dropping frames on older devices. That was the moment I stopped treating the animation like decoration and started treating it like product work.

The final version runs on requestAnimationFrame with particle physics (60 nodes, connection lines, and mouse repulsion). It is dynamically imported so the initial render is not held back by a heavy client bundle.

Code blocks use rehype-pretty-code with the Catppuccin Mocha theme for Shiki, so highlighting matches the rest of the palette. No surprise light-theme code block in the middle of a dark reading experience.

The frame drops I didn't expect

Early on, the canvas animation caused noticeable frame drops on older devices. The fix was straightforward: reduce the particle count and tighten how often distance calculations run, while keeping the visual result essentially the same.


Where MDX fits into the build (content to page)

This is the path your reader is taking, even though they only see text and sections:

From draft to published post
1. No CMS tab open
Write the post in one file

Each article starts as a single MDX file in the repo. That keeps drafting, editing, and reviewing as simple as the rest of the codebase.

2. Slug to page
Let Next turn it into a route

Next reads the file, resolves the slug, and renders the content through the shared MDX component system so the post matches the rest of the site.

3. Tooltips and visuals
Add only the interactive parts that earn their place

Most of the page stays simple. I only reach for custom components when they explain something better than another paragraph would.

In practice, it means your editing workflow stays simple: write in MDX, commit changes, and Next renders the content with consistent typography and component styling.


What I'd do differently next time

If I were starting over, I'd set up a small component library earlier. By the end, I had plenty of reusable pieces (ScrollReveal, section headers, card layouts), but I paid for that later with extra glue code.

I'd also profile performance earlier, before visual features harden into habits. Shipping teaches you what actually matters; profiling teaches you sooner.


Ship it, then polish the decisions

The fastest way to lose momentum is to keep chasing a perfect first version. I shipped, watched how the reading experience behaved, and then tightened details that had real impact: what to emphasize, what to simplify, and how to keep everything fast.