Building a Portfolio That Doesn't Suck
At one point, this portfolio looked finished. The grid was in place, the type felt right, and the dark mode toggle worked. Then I sat down to write the first real post and realized the problem immediately: the site looked polished, but it did not sound like me yet.
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.
A lot of that aesthetic direction came from Paco Coursey's site. It is the clearest example I know of a portfolio that stays out of its own way: quiet type, a simple grid, and motion that only shows up when something changes. I borrowed that restraint more than any single layout detail.
Next.js App Router (and why I stopped debating it)
I picked Next.js for the site shell: routing, layouts, static generation, and fast page delivery. For writings, I wanted long-form pages that still pull in custom visuals when a paragraph alone is not enough.
Pages and layouts: App Router
The UI shell (home, writings index, article 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: TSX pages in the repo
Writings are not MDX files in a content folder. They are TSX pages with shared article chrome and custom components only where they genuinely help. Each post is one route under app/writing/, 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. Satoshi carries headlines and body copy. JetBrains Mono handles code and small UI labels. The combination does something practical: it creates hierarchy you can feel while scanning without switching families every other line.
Every tweak was in service of readability: relaxed line height for body text, medium weight only on titles, and code styles that support the paragraph instead of hijacking it.
Satoshi
Headlines that stay calm at small sizes
One family carries the whole reading experience. Medium weight for titles, regular for body copy, and tight tracking only where the eye needs a landmark.
JetBrains Mono
Color with purpose (not decoration)
This version of the site is intentionally neutral. Light and dark modes share the same structure: grayscale surfaces, soft borders, and readable contrast. I did not want a theme that competed with the writing.
The small callout accents (warm story, cool info, green tip) handle emphasis inside articles. One rule kept everything honest: if something did not need attention, it did not get an accent.
The site stays mostly grayscale so typography and spacing do the work. Callout accents only appear where a block needs a little extra context, not as decoration across every surface. Values follow your current theme — click a row to copy.
Animation as feedback, not spectacle
Most motion on this site is reactive, not decorative:
- A staggered entrance on the home page, once per full load.
- Hover states on the grid confirm “yes, this is interactive.”
- Color shifts on links and list rows that stay under 200ms.
Writings and articles load instantly — no entrance animation there. When animation has a narrow job, you stop noticing it. The interface starts to feel responsive, not staged.
Technical details that changed performance (for real)
Code blocks stay lightweight — no syntax highlighter bundle on pages that do not need one. The home entrance runs once when the document loads and resets on a full browser reload, so client navigations to writings never replay it.
Where content fits into the build (draft to page)
This is the path your reader is taking, even though they only see text and sections:
1. One page file
Write the post as a route
Each article is a TSX page under app/writing/. The prose lives next to the components it uses, committed like any other UI work.
2. Shared shell
Wrap it in WritingArticle
Title, date, reading time, and nav come from a shared layout component so every post inherits the same typography and spacing.
3. Earned interactivity
Add visuals only when they teach
Most of the page stays plain paragraphs. Custom components show up when a diagram or comparison explains something faster than another block of text.
In practice, it means your editing workflow stays simple: write in a page file, 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 the writing components earlier. By the end, I had plenty of reusable pieces (callouts, diagrams, article chrome), but I paid for that later with extra glue code.
I'd also profile interaction performance earlier, before motion patterns 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.