Blog with MDX and RSS
The fastest way to not have a blog is to start by choosing a CMS. You will compare five of them, sign up for two, wire one halfway in, and write nothing. A blog needs a folder of files, a way to turn them into pages, and a feed so people can subscribe. That's the whole machine. Your posts as files in your own repo means version history for free, no vendor to outlive, and writing that happens in the same editor you already live in.
MDX is markdown that can embed components. The honest pitch: you will write plain markdown ninety-five percent of the time, and the five percent where you want an interactive chart or a demo embedded mid-post, MDX makes that possible without a rewrite. You are buying the escape hatch, not the habit.
The RSS feed is not optional, and it is the piece agents most reliably get wrong: absolute URLs where relative ones sneak in, valid dates, correct content types. The spec makes the feed a first-class requirement with its own checks, because a subtly broken feed fails silently. Nobody's reader updates, nobody tells you, and you conclude nobody reads the blog.
Prerequisites
- A GitHub account and a Vercel account (free tiers are fine).
- One real post drafted in markdown, even a rough one. Building a blog with only lorem ipsum in it means every layout decision gets made against fake text.
# Project: Personal blog with MDX posts and RSS
Build a writing site: an index of posts, a page per post, and an RSS
feed. Posts are MDX files in the repo. No CMS, no database, no auth.
## Stack
- Next.js (App Router) on Vercel, fully static output. Every page and
the feed are generated at build time.
- MDX via Next's supported MDX tooling. Syntax highlighting for code
blocks at build time (e.g., Shiki), not in the browser.
- Plain CSS or Tailwind. Typography is the product: comfortable measure
(60-75 characters per line), real line height, readable on a phone.
## Content model
Posts live in `content/posts/`, one `.mdx` file each, frontmatter:
- `title`: [post title]
- `slug`: [url-safe-slug] — becomes `/posts/[slug]`
- `date`: YYYY-MM-DD
- `description`: one or two sentences, used on the index and in the feed
- `draft`: true/false — drafts render in dev, are EXCLUDED from the
production build and the feed
Publishing a post = adding one file and pushing. No code changes.
## Pages
### Index (`/`)
- Site name and a one-line description: [what you write about].
- All non-draft posts, newest first: title, date, description. No
pagination until there are more than 30 posts; then simple
newer/older links.
### Post pages (`/posts/[slug]`)
- Title, human-readable date, then the rendered body.
- Code blocks highlighted, images from the repo with alt text,
external links open normally (no target="_blank" scattering).
- Each post's `<title>` is "[Post title] — [Site name]"; description
meta comes from frontmatter.
## RSS feed (`/rss.xml`) — treat as a real feature
- Valid RSS 2.0, generated at build time from the same post data as
the index. One source of truth; the feed can never disagree with
the site.
- Every URL in the feed is ABSOLUTE, built from a `SITE_URL` constant
defined in one place. Relative URLs in feeds break every reader.
- `pubDate` in RFC 822 format from the post's `date`.
- Item content: the `description`. Full-content feeds are a later
decision, not a default.
- A `<link rel="alternate" type="application/rss+xml">` tag in the
site head so feed readers can auto-discover it.
## Explicitly out of scope
- Comments, reactions, view counters.
- Tags/categories until there are 15+ posts to organize.
- Newsletter signup. RSS is the subscription mechanism for now.
## Done means
- `git clone`, install, run: index and sample posts render locally.
- A `draft: true` post shows in dev and is absent from the production
build and the feed.
- `/rss.xml` passes the W3C feed validator with zero errors.
- Adding a post file and rebuilding updates the index and the feed
with no other edits.Adaptation notes:
- If you will genuinely never embed a component, use plain markdown files instead of MDX and delete nothing else from the spec. The content model, the feed rules, and the draft flag all carry over unchanged.
- Migrating from an old blog: preserve the old URLs with redirects, or accept that every inbound link dies. Add a section listing old-URL to new-URL mappings and make the agent implement them as real 301s.
- For a project changelog or a company log rather than a personal blog, the same structure works; rename
poststoentriesand cut the description meta down to one line. - The mistake: styling the code-block theme for two days before writing post number two. The failure mode of every blog is post count, and no spec can fix that part for you.