- #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.
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
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:
- Writing MDX in VS Code with vim bindings is faster than any CMS editor I've used.
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:
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 feedQuick tour of what each does:
| Package | Purpose |
|---|---|
gray-matter | Parses YAML frontmatter from .mdx files (title, date, tags). |
reading-time | Calculates "X min read" from post body. |
rehype-pretty-code + shiki | VS Code-quality syntax highlighting at build time. Zero client JS. |
rehype-slug | Adds id attributes to headings so you can link to them. |
rehype-autolink-headings | Wraps headings in <a> tags so people can click-copy anchors. |
remark-gfm | GitHub Flavored Markdown (tables, strikethrough, task lists). |
@tailwindcss/typography | Sensible defaults for rendering MDX prose. One class: prose. |
lucide-react | Consistent lightweight icon set. |
@vercel/analytics | Private dashboard: page views, referrers, top posts. 50K events/month free. |
@upstash/redis | Powers the per-post "X reads" counter on each post. 10K commands/day free. |
feed | Generates /rss.xml at build time. |
Folder structure
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:
@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
| Item | Cost |
|---|---|
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.