Rehan Akbar
Rehan Akbar
All articles
September 15, 2026·4 min
  • #build-log
  • #nextjs
  • #mdx
  • #vercel
  • #tailwind

Building This Blog: Next.js + MDX + Vercel in One Evening

A build log. Why I picked this stack, what surprised me, and every dependency I installed so you don't have to figure it out.

·
4 min read
·
··· reads

This is post #2 and it's a live demo of content pillar #1: the build log. Here's exactly how I built the blog you're reading right now — choices, mistakes, and all the packages I installed.

Stack choices, with reasons

Note

Quick version: Next.js 16 App Router + MDX via next-mdx-remote-client + Tailwind v4 + Shiki syntax highlighting + Vercel deploy. Total cost: ~$10/year for the domain.

Why Next.js and not Astro / Hugo / Gatsby?

I already know Next. I use it on client work. Astro is great and I've used it for side projects, but switching frameworks for a personal blog when I'm already fast in Next is a bad trade. I want friction between "I have an idea" and "it's live" to be as low as possible.

Why MDX files in the repo and not a CMS?

Two reasons:

  1. Writing MDX in VS Code with vim bindings is faster than any CMS editor I've used.
  2. git push → live in ~60 seconds on Vercel. Preview URLs per PR, even solo.

Headless CMS options (Sanity, Contentful) go on the shelf until a non-technical collaborator needs to publish. That day is not today.

Why next-mdx-remote-client and not Contentlayer?

Contentlayer is unmaintained as of 2026. So is Contentlayer2. I wasted a weekend on this — don't repeat the mistake. next-mdx-remote-client is the actively-maintained path and renders MDX as Server Components, which is what you want in App Router.

Dependencies installed

Here's the exact install command I ran:

bash
npm install next-mdx-remote-client gray-matter reading-time
npm install rehype-pretty-code shiki rehype-slug rehype-autolink-headings remark-gfm
npm install @tailwindcss/typography lucide-react
npm install @vercel/analytics @upstash/redis feed

Quick tour of what each does:

PackagePurpose
gray-matterParses YAML frontmatter from .mdx files (title, date, tags).
reading-timeCalculates "X min read" from post body.
rehype-pretty-code + shikiVS Code-quality syntax highlighting at build time. Zero client JS.
rehype-slugAdds id attributes to headings so you can link to them.
rehype-autolink-headingsWraps headings in <a> tags so people can click-copy anchors.
remark-gfmGitHub Flavored Markdown (tables, strikethrough, task lists).
@tailwindcss/typographySensible defaults for rendering MDX prose. One class: prose.
lucide-reactConsistent lightweight icon set.
@vercel/analyticsPrivate dashboard: page views, referrers, top posts. 50K events/month free.
@upstash/redisPowers the per-post "X reads" counter on each post. 10K commands/day free.
feedGenerates /rss.xml at build time.

Folder structure

plaintext
rehanakbar-blog/
├── app/
│   ├── layout.tsx              # root layout, fonts, header/footer, analytics
│   ├── page.tsx                # home page
│   ├── blog/
│   │   ├── page.tsx            # all posts index
│   │   └── [slug]/page.tsx     # single post renderer
│   ├── about/page.tsx
│   ├── rss.xml/route.ts
│   └── api/
│       ├── views/[slug]/route.ts
│       └── subscribe/route.ts
├── content/posts/              # all .mdx files live here
├── components/
├── lib/posts.ts                # read/parse MDX + frontmatter
└── public/

Nice and boring. Boring is good for a blog.

One thing that surprised me: Tailwind v4

I started following install guides that assumed Tailwind v3 (tailwind.config.ts, darkMode: 'class', plugins array). The create-next-app template installed Tailwind v4, which ships its own PostCSS plugin and puts theming in a @theme block inside globals.css. No config file.

Here's what the theme block looks like:

css
@theme inline {
  --color-accent: #0b7a71;
  --font-sans: var(--font-inter);
  --font-mono: var(--font-jetbrains-mono);
  --max-width-prose: 680px;
}

Fewer files, less indirection. I'm a fan after one evening.

Cost breakdown

ItemCost
Domain (Cloudflare, .dev, yearly)~$10/yr
Hosting (Vercel Hobby)$0/mo
Vercel Analytics$0/mo
Upstash Redis (view counter)$0/mo
Buttondown (newsletter, under 100 subs)$0/mo
Total until I outgrow free tiers~$10/year

I can live with that.

What I'd do differently next time

Honestly, nothing major. The whole thing took one evening, most of which was me reading Tailwind v4 docs. If I was starting over:

  • I'd skip investigating Contentlayer2 entirely — 4 hours I can't get back.
  • I'd provision the Upstash DB from the Vercel Marketplace before writing the API route so I don't have to add the KV env vars by hand.
  • I'd move the Buttondown subscribe call server-side from day one (which I did, in /api/subscribe). Client-side Buttondown calls expose your API key in the browser. Don't do that.

End of build log. If you're building one too and get stuck, email me — happy to help.

Weekly Engineering Notes

Enjoyed this technical deep dive?

Every Monday I share build logs, architecture trade-offs, and performance benchmarks from production systems. No spam, unsubscribe anytime.