engineering·3 min read

How I built this portfolio (and the stack behind it)

A quick walkthrough of the stack, the file layout, and the choices that made this site fast to ship and easy to keep writing on.

This is the first post — and the obligatory "how the sausage is made" rundown. I wanted a site I could keep adding to without thinking about plumbing: just git push a markdown file and have it appear, no CMS, no database, no deploy step I have to babysit.

Here's what I ended up with.

The stack

const stack = {
  framework: 'Next.js 15 (App Router, RSC)',
  language: 'TypeScript',
  styling: 'Tailwind CSS + @tailwindcss/typography',
  content: 'MDX via next-mdx-remote + gray-matter',
  syntax:  'Shiki via rehype-pretty-code (dual theme)',
  theme:   'next-themes (class strategy)',
  font:    'Geist Sans + Geist Mono',
  rss:     'feed (single XML)',
  deploy:  'Vercel — push markdown, get a build'
};

No CMS, no headless API, no database. Posts are .mdx files in content/posts/. The whole site is statically generated at build time.

File layout

app/
  layout.tsx            // root: theme + font + header/footer
  page.tsx              // landing: hero + bio + works + posts + contact
  blog/
    page.tsx            // post index + tag filter
    [slug]/page.tsx     // MDX renderer
    tag/[tag]/page.tsx  // posts filtered by tag
  api/rss/route.ts      // RSS feed
  sitemap.ts            // generated from MDX list
  robots.ts
content/posts/*.mdx     // every post is just a file
lib/
  mdx.ts                // gray-matter + zod-validated frontmatter
  seo.ts                // shared metadata helper
  projects.ts           // featured-works data
  timeline.ts           // bio data
components/             // layout, home, blog, mdx

Each post has a small, validated frontmatter block:

---
title: "Some post"
description: "One-liner"
date: "2026-05-20"
tags: ["nextjs", "mdx"]
category: "engineering"
---

A zod schema in lib/mdx.ts catches typos in category at build time so a broken post never reaches the site.

Why this stack

A few opinions, briefly:

Markdown + Git + Vercel is the simplest content workflow that scales to "a personal site you'll actually keep updating."

  • Next.js App Router + RSC. Static at build time, server components everywhere, zero client JS for content pages. The landing page only ships JS for the theme toggle.
  • MDX over plain Markdown. I can drop a <Callout> or a chart component into a post without leaving prose. The 95% of posts that don't need it stay pure Markdown.
  • Shiki with dual themes. One token tree, two color schemes. Light/dark switching is a CSS variable swap — no client-side highlighting, no flash.
  • No CMS. A CMS is a server I have to remember exists. Git is a server I already use. The blog is just files; the editor is whatever editor I'm already in.

Adding a new post

# 1. create the file
$ touch content/posts/my-new-post.mdx
 
# 2. write it
$ $EDITOR content/posts/my-new-post.mdx
 
# 3. ship it
$ git add . && git commit -m "post: my new post" && git push

Vercel sees the push, builds, deploys. Total ceremony: zero.

What's next

A few things I want to add but didn't on day one — kept out so I could ship:

  • Newsletter (probably ConvertKit/Buttondown — last)
  • Comments (giscus) — if/when posts get any traffic
  • /uses page — what I run day-to-day
  • A real avatar — that emoji 🦕 is a placeholder

If anything here is useful or you want to swap notes on the stack, the contact section below is the fastest way to reach me.