Build Guide
How to Build a Blog
Building a blog from scratch teaches you more about web performance, SEO, and content architecture than any tutorial — and an AI-assisted one can publish daily.
A blog is the foundation of every content-driven business. Build one from scratch and you'll learn SSG, MDX, image optimisation, RSS feeds, and SEO metadata.
Data model
The core tables you'll need before writing any UI.
Build order
The sequence that minimises rewrites — build in this order.
MDX file-based posts
Create a /content/posts/ directory. Each post is a .mdx file with frontmatter (title, date, slug, tags, excerpt). Use gray-matter to parse frontmatter and next-mdx-remote or Contentlayer to render MDX.
Post listing page
Read all .mdx files at build time using fs.readdirSync. Sort by date. Render a grid of post cards — title, date, excerpt, cover image, tags. This is fully statically generated.
Post detail page
Create /blog/[slug]/page.tsx. Read the matching .mdx file. Render the MDX content. Add a table of contents from heading nodes. Show author, date, reading time (word count ÷ 200).
SEO metadata
Add generateMetadata() to each page. Set title, description from frontmatter, canonical URL, og:image (use the cover image or generate one with @vercel/og), og:type = "article", publishedTime.
Tag pages
Create /blog/tag/[tag]/page.tsx. Filter posts by the tag. Generate static params from all unique tags across all posts. Link tags in the post card to their tag page.
RSS feed
Create /feed.xml/route.ts. Loop through all posts, generate an RSS XML string with title, link, description, pubDate per item. Return it with Content-Type: application/rss+xml.
Newsletter subscribe
Add an email capture form below each post. POST the email to /api/subscribe, which adds it to Resend's audience. Send a welcome email immediately via Resend.
Done when
Observable behaviors that confirm V1 is complete — verify each one before you ship.
- ✓
Post listing page shows all posts sorted by date with correct reading-time estimates
- ✓
RSS feed at /feed.xml validates correctly in an RSS reader
- ✓
Sitemap at /sitemap.xml lists every post URL with its lastModified date
- ✓
Email subscribe form adds the address to Resend audience within 60 seconds
- ✓
Lighthouse scores 90+ on all metrics for a post detail page
First Run Requirement
Ship with 3 example posts in /content/posts/ — a welcome post, a how-to guide, and an opinion piece. Blog listing page is never empty on first deploy.
Build it with AI — Architect Prompt
Paste this into Claude, Cursor, Windsurf, or any AI coding tool. It includes the full context of this guide — data model, build order, done conditions, and pitfalls — so the AI starts with everything it needs.
<context>
App: Blog
Difficulty: Beginner
Estimated build time: 1–3 days
Tech stack (intermediate): Next.js + Contentlayer + Sanity CMS — editorial workflow with visual editing
Data model:
Post: slug, title, excerpt, content (MDX), author, tags[], publishedAt, updatedAt, coverImage, featured
Tag: slug, name, description — derived from posts, no separate table needed for MDX-based blog
FIRST RUN REQUIREMENT:
Ship with 3 example posts in /content/posts/ — a welcome post, a how-to guide, and an opinion piece. Blog listing page is never empty on first deploy.
DONE WHEN — verify each before marking V1 complete:
□ Post listing page shows all posts sorted by date with correct reading-time estimates
□ RSS feed at /feed.xml validates correctly in an RSS reader
□ Sitemap at /sitemap.xml lists every post URL with its lastModified date
□ Email subscribe form adds the address to Resend audience within 60 seconds
□ Lighthouse scores 90+ on all metrics for a post detail page
Recommended build order:
1. Define API contract + schema + seed data (always first)
2. MDX file-based posts — Create a /content/posts/ directory. Each post is a .mdx file with frontmatter (title, date, slug, tags, excerpt). Use gray-matter to parse frontmatter and next-mdx-remote or Contentlayer to render MDX.
3. Post listing page — Read all .mdx files at build time using fs.readdirSync. Sort by date. Render a grid of post cards — title, date, excerpt, cover image, tags. This is fully statically generated.
4. Post detail page — Create /blog/[slug]/page.tsx. Read the matching .mdx file. Render the MDX content. Add a table of contents from heading nodes. Show author, date, reading time (word count ÷ 200).
5. SEO metadata — Add generateMetadata() to each page. Set title, description from frontmatter, canonical URL, og:image (use the cover image or generate one with @vercel/og), og:type = "article", publishedTime.
6. Tag pages — Create /blog/tag/[tag]/page.tsx. Filter posts by the tag. Generate static params from all unique tags across all posts. Link tags in the post card to their tag page.
7. RSS feed — Create /feed.xml/route.ts. Loop through all posts, generate an RSS XML string with title, link, description, pubDate per item. Return it with Content-Type: application/rss+xml.
8. Newsletter subscribe — Add an email capture form below each post. POST the email to /api/subscribe, which adds it to Resend's audience. Send a welcome email immediately via Resend.
9. End-to-end verification — walk every Done When condition above (always last)
Known pitfalls to avoid:
- Using getServerSideProps instead of generateStaticParams — blog posts don't change in real time. Static generation means zero server cost and instant page loads. Use SSG (generateStaticParams) for all posts.
- Forgetting to add a sitemap — Google won't find new posts without one. Create /sitemap.xml/route.ts that lists all post URLs with their lastModified date.
- Embedding images without optimisation — always use Next.js <Image> component, never <img>. It handles WebP conversion, lazy loading, and responsive sizing automatically.
</context>
<role>
You are a Senior Full-Stack Engineer and product architect who has shipped production Blogs before. You know exactly where developers get stuck and how to structure the project to avoid rewrites.
</role>
<task id="step-1-clarify">
Before writing any code or spec, ask me 3–5 clarifying questions that will meaningfully change the architecture. Focus on: scale expectations, auth requirements, platform (web / mobile / both), must-have vs nice-to-have features for the MVP, and any hard constraints (budget, deadline, existing infrastructure).
<example>
BAD: "What tech stack do you want to use?" — too broad, doesn't change architecture decisions.
GOOD: "Do you need real-time sync across devices, or is single-device with periodic refresh acceptable? This decides whether we use WebSockets or simple REST polling and significantly affects infrastructure complexity."
</example>
⚠️ Do NOT start planning or writing code until I answer. Present the questions, then stop and wait.
</task>
<task id="step-2-architect">
After I answer your questions, produce the following in order:
1. TECHNICAL SPECIFICATION
- System architecture using → to show data and event flow
- Core data model (refer to data model in <context>)
- API contract — list ALL routes with request/response shapes BEFORE any implementation
WHY: agreeing on contracts first prevents rewrites when frontend and backend shapes diverge
- External APIs and integration points
2. MVP PLAN in three phases:
SETUP ✦ Step 1 (always): API contract + schema migration + seed data
Done when: schema is migrated, seed script runs, app starts with demo data, zero manual setup.
CORE FEATURES — build in this exact order:
2. MDX file-based posts
3. Post listing page
4. Post detail page
5. SEO metadata
6. Tag pages
7. RSS feed
8. Newsletter subscribe
DONE WHEN — one observable condition per feature:
□ Post listing page shows all posts sorted by date with correct reading-time estimates
□ RSS feed at /feed.xml validates correctly in an RSS reader
□ Sitemap at /sitemap.xml lists every post URL with its lastModified date
□ Email subscribe form adds the address to Resend audience within 60 seconds
□ Lighthouse scores 90+ on all metrics for a post detail page
STRETCH GOALS — post-launch additions that are explicitly out of V1 scope.
3. BLOCKER ANALYSIS
Flag: API rate limits, auth complexity, scope risks, cold-start problems, top 2–3 failure modes.
FIRST RUN REQUIREMENT: Ship with 3 example posts in /content/posts/ — a welcome post, a how-to guide, and an opinion piece. Blog listing page is never empty on first deploy.
✓ Done when: app launches with seed data and the full user journey works without any manual setup.
<self_check>
Before presenting your output, verify:
□ Every answer I gave to clarifying questions is reflected in the spec
□ Step 1 is ALWAYS: API contract + schema + seed — never UI first
□ Done When criteria are observable user behaviors, not internal states
□ The plan is realistic for one person to ship within 1–3 days
□ All known pitfalls from <context> are addressed in the spec
</self_check>
</task>
<task id="step-2.5-agents-md">
Generate an AGENTS.md file at the project root. This file is read automatically by Claude Code, Cursor, Windsurf, and all major AI coding tools at the start of every session.
Include:
- Project overview (2–3 sentences)
- Tech stack with exact version numbers
- Folder structure with one-line descriptions per directory
- Key architectural decisions and the WHY behind each
- Coding conventions: camelCase components, kebab-case files, max 200 lines per file, one concern per file
- Available commands: dev, build, test, lint, db:migrate, db:seed
- MVP scope boundaries — features explicitly out of V1
Note in the file: "Cursor users: symlink .cursorrules → AGENTS.md. Claude Code users: symlink CLAUDE.md → AGENTS.md."
</task>
<task id="step-3-implement">
Implement the full project in the exact order from step 2. For each step:
- Write complete code (no placeholders or TODOs)
- Confirm the step works before moving to the next
<constraints>
- Step 1 is ALWAYS: define all API routes + TypeScript request/response interfaces + run schema migration
WHY: agreeing on contracts before writing a single component prevents shape mismatches that require rewrites
- Final step is ALWAYS: start the app and walk every Done When condition from <context> end-to-end
WHY: a spec that passes unit tests but breaks the core user journey is not done
- Max 200 lines per file. WHY: every file must fit in one AI context window for complete reasoning
- One concern per file. WHY: mixing auth logic into API routes makes it impossible to reuse — extract to lib/auth.ts
- TypeScript strict mode, no `any` types. WHY: catches data shape mismatches at compile time, not in production
- All database queries in server components or API routes only. WHY: keeps credentials server-side
- All environment variables documented in .env.example. WHY: next developer sets up in under 5 minutes
</constraints>
</task>How to use this prompt
- 1.Copy the prompt above
- 2.Open Claude, Cursor (Cmd+L), or Windsurf
- 3.Paste the prompt and send — the AI will ask 3–5 clarifying questions
- 4.Answer the questions, then it generates your full project spec
- 5.Continue in the same session to implement step by step
Common mistakes
What trips up most developers building this for the first time.
Using getServerSideProps instead of generateStaticParams — blog posts don't change in real time. Static generation means zero server cost and instant page loads. Use SSG (generateStaticParams) for all posts.
Forgetting to add a sitemap — Google won't find new posts without one. Create /sitemap.xml/route.ts that lists all post URLs with their lastModified date.
Embedding images without optimisation — always use Next.js <Image> component, never <img>. It handles WebP conversion, lazy loading, and responsive sizing automatically.
Recommended tech stack
Pick the level that matches your experience.
Next.js + MDX files + Vercel — static blog, zero backend, deploy in minutes
Next.js + Contentlayer + Sanity CMS — editorial workflow with visual editing
Next.js + Keystatic CMS + Claude API (AI drafts) + Resend (newsletter) + Algolia (search)
Take it further — related ideas
Each comes with revenue math, a full build guide, and a prompt to paste into Claude or Cursor.