Building an Astro Blog with Claude Code
Instead of following a tutorial, I gave Claude one prompt and watched it install Astro, wire up Tailwind, MDX, RSS, and a sitemap, push to GitHub, and deploy to Vercel — all in one session.
Contents
In part 1 I explained why I moved from Lovable.dev to Astro for SEO. Lovable builds React apps that serve empty HTML to Googlebot — Astro builds static HTML that Google can read instantly, no JavaScript required.
This post is part 2: actually building the blog. But instead of walking you through every command manually, I handed it to Claude.
The Prompt
One prompt. That’s it.
Build me an SEO-ready Astro blog from scratch with Tailwind, MDX, RSS feed, sitemap, and fonts
From that, Claude installed everything, built the full project structure, and had a working blog ready to deploy. Here’s exactly what happened — including the decisions it made that I didn’t ask for, and where it hit problems.
What Claude Installed
Claude ran the following commands in sequence:
npm create astro@latest astro-demo
When prompted:
- Template: Empty
- TypeScript: Yes, strict
- Install dependencies: Yes
- Initialize git repo: Yes
Then added the integrations:
npx astro add tailwind
npx astro add mdx
Then fonts and RSS:
npm install @fontsource-variable/inter @fontsource/jetbrains-mono
npm install @astrojs/rss
One thing it caught that I didn’t ask about: it skipped @astrojs/sitemap entirely. That integration crashes on Astro 4.16+ with a Cannot read properties of undefined (reading 'reduce') error. Claude knew this and built a manual sitemap endpoint at src/pages/sitemap.xml.ts instead. That’s the kind of thing you only know if you’ve hit the bug — or in this case, if your agent has enough context to anticipate it.
What Claude Built
The full project structure:
src/
├── components/
│ ├── Header.astro
│ ├── PostList.astro
│ └── SeriesNav.astro
├── content/
│ └── posts/ # .md/.mdx files go here
├── layouts/
│ └── BaseLayout.astro
├── pages/
│ ├── index.astro
│ ├── about.astro
│ ├── archive.astro
│ ├── posts/[slug].astro
│ ├── tags/[tag].astro
│ ├── categories/[category].astro
│ ├── series/[series].astro
│ ├── rss.xml.ts
│ └── sitemap.xml.ts
└── styles/
└── globals.css
The scope here is worth noting. I asked for a blog. Claude built a full content taxonomy — tags, categories, series navigation, archive page, RSS feed, sitemap. Every route it created serves a purpose for either discoverability or SEO.
The SEO Decisions Baked Into BaseLayout
This is the part most tutorials skip. The layout file is where most of your SEO either works or doesn’t.
Claude built BaseLayout.astro with the following automatically wired up for every page:
Canonical URL — generated from the site URL and current path. Every page declares its own canonical, which prevents duplicate content issues if Vercel’s preview URLs ever get indexed.
Open Graph tags — og:title, og:description, og:image, og:url, og:type. These control how the page appears when shared on Twitter, LinkedIn, or Slack. Claude wired them to the same frontmatter fields as the page meta, so there’s no separate OG configuration needed per post.
Twitter Card — summary_large_image by default, so post shares show a full image preview rather than a small thumbnail.
JSON-LD structured data — three schemas per post page: Article, WebSite, and BreadcrumbList. These are what Google uses to understand the page type, authorship, and navigation hierarchy. On a CSR site this has to load via JavaScript and might not be seen by crawlers. On Astro it’s in the HTML before any JavaScript runs.
All of this is automatic. Drop a new post file in src/content/posts/, push, and every one of those meta tags is generated correctly with no manual input.
The Content Schema
Claude defined the content schema in src/content/config.ts:
title: string
description: string
publishDate: string
updatedDate?: string
tags: string[]
category: 'guides' | 'notes' | 'builds'
draft: boolean
image?: string
imageAlt?: string
series?: string
part?: number
The series and part fields were something I asked for specifically — I knew I’d want to group related posts. Claude built SeriesNav.astro to handle the series UI automatically: previous/next links within a series, plus a link back to the series index page. Posts without series in their frontmatter render as standalone, no extra configuration needed.
Where It Hit Problems
It wasn’t perfect. Two things needed manual fixes.
The Vercel deploy config. The first deploy failed because Vercel defaulted to the wrong output directory. Claude had to add a vercel.json file to the root:
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"framework": "astro"
}
After that it deployed cleanly.
The GitHub push from the server. Claude Code runs on my homeserver, not my local machine. To push to GitHub from the server, the git remote needs a personal access token embedded in the URL — standard HTTPS auth doesn’t work in a headless environment. Claude flagged this and set the remote correctly:
git remote set-url origin https://TOKEN@github.com/username/repo
Not a Claude Code limitation — just a server environment reality. Worth knowing if you’re running agents on a remote machine.
Push to GitHub and Deploy
Once the remote was configured:
git add .
git commit -m "Initial blog build"
git push origin master
Then on Vercel: Add New Project → import the repo → Deploy. Vercel detects Astro automatically. No build settings needed beyond the vercel.json fix above.
Within two minutes the blog was live at a Vercel URL.
The Result
A fully working, SEO-ready Astro blog — deployed on Vercel, with tags, categories, series navigation, RSS, sitemap, JSON-LD schema, Open Graph, and canonical URLs — built from a single prompt in one session. The whole thing took just over 7 minutes of active Claude time.
Live demo: astro-demo-theta-tawny.vercel.app
What This Actually Demonstrates
The point isn’t that Claude wrote the code. It’s that agentic workflows collapse the gap between a decision and a working system.
The traditional path for this project: read the Astro docs, pick a template, configure Tailwind, wire up MDX, figure out why @astrojs/sitemap crashes, build the content schema, write the base layout, set up JSON-LD manually, troubleshoot the Vercel config, push. Half a day minimum, probably longer.
The agentic path: one prompt, review the output, fix two issues, ship.
The output is the same. The time investment is not.
That’s what this blog documents — not AI as a novelty, but AI as operational infrastructure. The blog itself was built the same way the systems it writes about are built: give the agent a clear goal, stay in the loop on decisions that matter, and let it handle the implementation.
Why the SEO Foundation Matters More Than the Framework
A lot of people building blogs with AI tools focus on the wrong thing. They want the blog to look good, deploy fast, and be easy to write in. Those matter. But the SEO foundation — the stuff that determines whether Google can read and understand your content — is decided before you write a single post.
Here’s what this build gets right by default:
Every page is crawlable on the first request. No JavaScript rendering required. Googlebot hits the URL, gets complete HTML, indexes it. That’s it.
Structured data is present and correct on every post. The JSON-LD schema Claude wired into BaseLayout.astro tells Google this is an Article, who authored it, when it was published, and where it sits in the site hierarchy. This is what enables rich results in SERPs — date stamps, breadcrumbs, author attribution. On a manually-built blog these are easy to get wrong or forget entirely. Here they’re generated automatically from frontmatter.
The sitemap updates on every deploy. The custom sitemap.xml.ts endpoint queries the content collection and generates a fresh sitemap at build time. Every new post is automatically included with its correct URL and publish date. You never manually update a sitemap or worry about a new post being missed.
RSS keeps returning readers engaged. The RSS feed at /rss.xml is a low-effort retention mechanism. Readers who subscribe get notified of new posts without you needing to send an email or post on social. For a technical blog, a meaningful chunk of the audience prefers RSS — it’s worth having from day one.
None of this required separate configuration. It was all handled in the initial build.
Write Your First Post
Drop a .md file in src/content/posts/:
---
title: "My First Post"
description: "A short description for Google."
publishDate: "2026-04-27"
tags: ["astro", "seo"]
category: "guides"
draft: false
---
Your content here.
Push to GitHub. Vercel redeploys automatically. Done.