building-in-public

How I used Claude to build a CMS-powered blog on my personal site in one afternoon

I connected Sanity CMS to my Next.js site using Claude Code. Here's the exact workflow, the technical decisions, and why each one mattered for SEO.

Usama Khan
Usama KhanApril 3, 20263 min read
headless cms

I needed a blog on my personal site. Not a WordPress install. Not a Substack embed. A proper blog that lives on my domain, matches my design system, and gives me full control over SEO metadata.

I built it in one afternoon using Claude Code as my development partner. Here's exactly how it worked and why I made the technical choices I did.

Why the CMS choice mattered

I went with Sanity as a headless CMS. The reasoning was simple. My site runs on Next.js deployed on Vercel. Sanity integrates natively with both. It gives me a writing dashboard where I can draft, edit, and publish posts. The content gets fetched server-side and rendered as static HTML on my domain.

That last part is critical. If your blog content is rendered client-side with JavaScript, Google can still crawl it, but LLMs often can't. ChatGPT, Perplexity, and Claude rely on clean HTML to parse and cite your content. Server-rendered pages give them exactly that.

The setup workflow

I gave Claude Code a detailed prompt covering the full scope. Install Sanity dependencies, create the content schema, build the blog listing page, build individual post pages, wire up SEO metadata, and embed the CMS backend in my app.

It handled the entire setup in a single session. The schema includes fields for title, slug, category, meta description, featured image, author, publish date, tags, and a rich text body with support for headings, links, images, and blockquotes.

Connecting Sanity to the live site

The connection is straightforward. Sanity gives you a project ID when you create a project. That ID goes into your environment variables. Your Next.js app uses the Sanity client library to fetch content from their API using that ID. No database to manage. No server to maintain.

On Vercel, I added the project ID and dataset name as environment variables. On Sanity's side, I added my domain as a CORS origin so the studio could read and write data. That was it.

Why every blog page needs its own metadata

Each post page dynamically generates its own meta title, description, Open Graph tags, canonical URL, and JSON-LD schema. This is not optional. Without dynamic metadata, every blog post shares the same generic title and description in search results. That kills click-through rates.

The JSON-LD BlogPosting schema tells Google and LLMs exactly what this page is: a blog post, by a specific author, published on a specific date, about a specific topic. Structured data makes your content easier to parse for both search engines and AI systems.

The sitemap and robots.txt

I added a dynamic sitemap that fetches all published posts from Sanity and includes their URLs automatically.

Every time I publish a new post, it shows up in the sitemap without any manual work.

The robots.txt allows all crawlers (including GPTBot, PerplexityBot, and Claude-Web) and blocks only the admin dashboard. I also added an llms.txt file, which is an emerging standard that helps LLM crawlers understand what content is available on the site.

What I would do differently

Not much. The one issue I ran into was caching. Blog post pages were statically generated at build time, which meant new posts returned a 404 until I redeployed. The fix was switching to dynamic server rendering. Slightly slower per request, but new posts work immediately.

If you are building a content-driven site on Next.js, start with dynamic rendering and optimize later. Debugging caching issues costs more time than the milliseconds you save.

Usama Khan
Usama Khan

Usama is an AI SEO strategist for B2B brands. He helps businesses rank on Google and get recommended by LLMs like ChatGPT. He also watches too much cricket.