How I Built This Blog
The stack behind this blog — MDX, Shiki, Sandpack playgrounds, and the authoring pipeline that ties it all together.
Every developer eventually builds their own blog. It's a rite of passage — part portfolio piece, part creative outlet, part excuse to yak-shave for weeks on tooling instead of actually writing.
This is mine. It's built with Next.js, MDX, and a handful of custom widgets that make writing technical posts more fun (and hopefully more useful to read). This post walks through the pieces and doubles as a living demo of everything the system can do.
The stack#
The blog runs on:
- Next.js 16 with the App Router — each post is a statically generated route at build time via
generateStaticParams - MDX via
next-mdx-remote/rsc— posts are plain.mdxfiles compiled on the server as React Server Components - Shiki (through
rehype-pretty-code) — syntax highlighting with dual light/dark themes that match the site's color mode - Sandpack — embedded, runnable React playgrounds powered by CodeSandbox's browser bundler
- Custom components —
Asidecallouts,CodeSnippetblocks with copy support, andPlaygroundwrappers with multi-file and dependency support
Posts live as .mdx files in a content/blog/ directory. Each one starts with a frontmatter block:
---
title: "My Post"
description: "One-sentence summary for cards and SEO metadata."
publishedAt: "2026-03-10"
updatedAt: "2026-03-10"
tags: ["nextjs", "mdx"]
draft: false
slug: "optional-custom-slug"
---Only title, description, and publishedAt are required. The draft flag hides work-in-progress posts from production while keeping them visible in development, and slug defaults to the filename if omitted. Every post automatically gets OpenGraph metadata, a sitemap entry, and an RSS feed entry.
Code snippets#
Any fenced code block gets syntax highlighting via Shiki and a copy button for free. The language tag determines the theme:
export const normalizeSlug = (value: string) =>
slugify(value, { lower: true, strict: true, trim: true });Shiki runs at build time, so there's zero client-side JavaScript for highlighting — just pre-rendered HTML with inline styles. It supports dual themes (github-light and github-dark) that swap automatically with the site's color mode.
export function Greeting({ name }: { name: string }) {
return <h1 className="text-2xl font-bold">Hello, {name}!</h1>;
}Asides#
For callouts, tips, and warnings, the Aside component renders a toned callout box. It supports info, warning, success, and danger tones, and you can nest any markdown content inside — including code blocks.
Playgrounds#
This is the feature I'm most excited about. For React examples, the Playground component embeds a live Sandpack editor directly in the post. You can edit the code and see the result immediately:
Gradient card
tsxMulti-file examples#
For anything more complex, playgrounds support multiple files with tabs. Each file is wrapped in a PlaygroundFile component:
Multi-file components
tsxExternal dependencies#
Playgrounds can pull in npm packages too. Sandpack installs them on the fly:
With npm dependency
tsxFor non-JS/TS languages, the playground falls back to a static code snippet with an optional link to an external sandbox:
Python example
Inline playground execution is limited to JavaScript/TypeScript (React). For languages like Python, Rust, or Go, pass an externalUrl and keep this snippet as a readable fallback.
def greet(name: str) -> str:
return f"hello {name}"
print(greet("world"))Machine-readable by default#
Every post on this blog supports content negotiation. Request a post with Accept: text/markdown (or just use curl) and you'll get the raw markdown instead of HTML:
curl https://datta.me/blog/how-i-built-this-blogThe proxy layer detects CLI user agents and markdown-preferring clients automatically, so the same URL serves HTML to browsers and markdown to agents, scrapers, and CLI tools. If you're building something that consumes blog content programmatically — an LLM agent, a feed reader, a research tool — every post is already accessible as plain text without any scraping.
Diagrams with Mermaid#
Sometimes a diagram says more than a paragraph. Fenced code blocks with the mermaid language tag render as interactive diagrams that adapt to light and dark mode:
What's next#
There's more I want to add — a table of contents sidebar, view count tracking, and maybe some post-specific interactive widgets beyond the generic playground. But the foundation is solid enough to start writing, and that's the point. The best blog engine is the one that gets out of the way and lets you publish.