DEV Community

Ted
Ted

Posted on • Originally published at tedagentic.com

Building an Astro Blog with Claude Code

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then fonts and RSS:

npm install @fontsource-variable/inter @fontsource/jetbrains-mono
npm install @astrojs/rss
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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. Tag and category pages in particular matter for topic clustering: they give Google a signal that this site covers a coherent subject area, not just a loose collection of posts, which helps individual posts inherit topical authority over time.

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 tagsog: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 Cardsummary_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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

One security note: treat this token like a password. Don't commit it, don't leave it in shell history. Either use a fine-grained GitHub token scoped to the single repo, or switch to SSH keys if you're on a machine you control long-term.

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
Enter fullscreen mode Exit fullscreen mode

Then on Vercel: Add New Project → import the repo → Deploy. Vercel detects Astro automatically but needs one config file to correctly set the output directory — that's the vercel.json above. Once it's there, no further build configuration is needed.

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 full build ran in just over 7 minutes.

(Demo video coming soon.)

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.
Enter fullscreen mode Exit fullscreen mode

Push to GitHub. Vercel redeploys automatically. Done.


The build is the easy part. The harder question is what you do next — how you monitor whether Google is actually finding and indexing your posts, and what to do when something goes wrong. That's what the rest of this series covers. Browse all posts in the Astro SEO Blog series.

Top comments (0)