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, mdxEach 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 pushVercel 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
/usespage — 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.