How I Used Claude to Build claudefan.dev
A deep dive into building a modern tech blog using Claude AI as my pair programming partner. From architecture decisions to deployment — here's how AI-assisted development actually works.
Mohan Kumar
Author
Building a website from scratch is no small feat. Building one while documenting every decision, implementing modern best practices, and shipping in a single day? That’s where AI-assisted development changes the game.
This post is a technical deep-dive into how I built claudefan.dev using Claude Opus as my pair programming partner. If you’re a developer curious about what AI-assisted development actually looks like in practice, this is for you.
The Tech Stack
Before diving into the implementation, here’s what powers this site:
| Technology | Purpose |
|---|---|
| Astro 5 | Static site framework |
| Tailwind CSS 4 | Utility-first styling |
| MDX | Markdown with components |
| TypeScript | Type safety |
| Vercel | Deployment & hosting |
| Google Analytics | Traffic analytics |
| n8n Webhooks | Email subscription handling |
Architecture Overview
The site follows a clean, component-based architecture:
src/
├── components/
│ ├── blog/ # Article cards, TOC, navigation
│ ├── layout/ # Header, Footer, Sidebar
│ └── ui/ # Buttons, forms, theme toggle
├── content/
│ └── blog/ # MDX blog posts
├── layouts/
│ ├── BaseLayout.astro
│ └── BlogLayout.astro
├── pages/
│ ├── index.astro
│ ├── about.astro
│ └── blog/[...slug].astro
├── lib/
│ ├── config.ts # Site configuration
│ └── utils.ts # Helper functions
└── styles/
└── global.css # Tailwind + custom styles
The Claude-Assisted Development Flow
Here’s what my workflow looked like:
- Describe the goal — I tell Claude what I want to build
- Claude implements — It writes the code, often across multiple files
- I review and test — Check the browser, verify the behavior
- Iterate — Request adjustments or fixes as needed
This tight feedback loop is incredibly productive. Let me walk through some concrete examples.
Building the Blog Layout
One of the more complex features was the three-column blog layout, inspired by OpenAI’s blog design:
┌──────────────────────────────────────────────────────────┐
│ Header (Fixed) │
├──────────────────────────────────────────────────────────┤
│ │ │ │
│ (empty) │ Article Content │ Table of │
│ │ - Title │ Contents │
│ │ - Body │ (Fixed) │
│ │ - Tags │ │
│ │ │ │
└──────────────────────────────────────────────────────────┘
Here’s the key CSS structure I landed on:
<div class="relative pt-24 pb-12">
<!-- Table of Contents (Fixed Right Side) -->
{headings.length > 0 && (
<aside class="hidden xl:block fixed right-8 top-24 w-56">
<TableOfContents headings={headings} />
</aside>
)}
<!-- Main Article Content -->
<article class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Content here -->
</article>
</div>
The key insight: using fixed positioning for the TOC keeps it visible during scroll, while max-w-3xl mx-auto centers the content naturally. No complex grid calculations needed.
Dark Mode with Tailwind CSS 4
Tailwind CSS 4 introduces a new approach to theming. I used CSS custom properties combined with the new @theme directive:
@theme {
/* Brand Colors */
--color-bg: #F4F1ED;
--color-bg-dark: #1a1a1a;
--color-text-main: #1D1D1D;
--color-text-main-dark: #F4F1ED;
--color-accent: #D97757;
/* Fonts */
--font-heading: 'Playfair Display', serif;
--font-sans: 'Inter', sans-serif;
}
/* Dark mode variable overrides */
html.dark {
--color-bg: var(--color-bg-dark);
--color-text-main: var(--color-text-main-dark);
}
The theme toggle persists to localStorage and respects system preferences on first load.
Subscribe Form with Webhook Integration
Instead of a complex backend, I used n8n webhooks for email collection. Here’s the subscribe form implementation:
const WEBHOOK_URL = 'https://n8n.neupilot.com/webhook/...';
async function handleSubscribe(form, emailInput, button) {
const email = emailInput.value.trim();
// Validation
if (!isValidEmail(email)) {
showMessage(form, 'Please enter a valid email.', true);
return;
}
// Show loading state
button.disabled = true;
button.innerHTML = 'Subscribing...';
try {
const response = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, source: 'claudefan.dev' })
});
if (response.ok) {
showMessage(form, 'Thanks for subscribing!', false);
emailInput.value = '';
}
} catch (error) {
showMessage(form, 'Something went wrong.', true);
}
}
This keeps the frontend simple while offloading email handling to a no-code automation platform.
Branding: The Little Details
The site branding uses a simple but effective approach:
<a href="/" class="text-lg font-bold tracking-tight">
claudefan<span class="text-accent">.dev</span>
</a>
The .dev suffix in terracotta (#D97757) creates visual interest without needing a logo. The favicon is a simple SVG lightbulb:
<svg viewBox="0 0 24 24" fill="none" stroke="#D97757" stroke-width="2">
<path d="M9 18h6"/>
<path d="M10 22h4"/>
<path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8
6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/>
</svg>
Google Analytics Integration
Analytics setup was straightforward — adding the gtag snippet to the base layout:
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXX');
</script>
<!-- Rest of head content -->
</head>
Since all pages use BaseLayout.astro, analytics automatically track every page.
Content Architecture
Blog posts use Astro’s content collections with a well-defined schema:
// src/content/config.ts
const blogCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string(),
publishedAt: z.date(),
author: z.string(),
category: z.enum(["tutorials", "guides", "announcements", ...]),
tags: z.array(z.string()),
coverImage: z.object({
src: z.string(),
alt: z.string()
}).optional(),
featured: z.boolean().default(false),
draft: z.boolean().default(false),
}),
});
This gives us type-safe frontmatter validation and IDE autocomplete.
What Claude Did Well
Looking back at this project, here’s where Claude excelled:
-
Multi-file refactoring — When I asked to update the logo across all files, Claude found and updated Header, Footer, and the homepage in one go.
-
CSS layout problem-solving — The blog layout went through several iterations. Claude understood spatial relationships and could fix alignment issues from a screenshot description.
-
Boilerplate generation — Component structure, form handling, webhook integration — Claude handled the repetitive parts quickly.
-
Context retention — Throughout the session, Claude remembered our design system (terracotta accent, Playfair headings, etc.) and applied it consistently.
What Required Human Judgment
Some things still needed my input:
- Design taste — Choosing the color palette, typography, overall aesthetic
- Architecture decisions — What to build vs. what to outsource (n8n for email handling)
- Content strategy — What to write about, how to structure the blog
- Quality review — Checking the browser, catching visual bugs
AI accelerates execution, but strategy and taste remain human domains.
Deployment & Performance
The site deploys to Vercel via GitHub integration. Every push triggers a build:
npm run build # Astro builds static HTML
# Output: dist/ folder with optimized assets
Astro’s partial hydration means minimal JavaScript. The site loads fast and works without JS enabled.
Lessons Learned
-
Start with a clear vision — AI works best when you know what you want. Vague requests produce vague results.
-
Iterate in small steps — Instead of asking for a complete page, build and verify component by component.
-
Provide context — Screenshots, reference sites, and specific examples help Claude understand your intent.
-
Review everything — AI can introduce subtle bugs or make assumptions. Always verify in the browser.
-
Trust but verify — Claude’s code is usually correct, but edge cases and styling details often need adjustment.
The Numbers
Here’s what I shipped in a single day:
- 6 pages (Home, Blog, Categories, About, individual posts)
- 15+ components (Header, Footer, ArticleCard, TOC, SubscribeForm, etc.)
- Dark mode with persistent theme toggle
- Newsletter signup with webhook integration
- Google Analytics for traffic monitoring
- Full responsive design (mobile, tablet, desktop)
Conclusion
Building claudefan.dev with Claude wasn’t about replacing my skills — it was about amplifying them. I made the design decisions, defined the architecture, and maintained quality standards. Claude handled the implementation details, boilerplate, and tedious refactoring.
The result? A complete, polished website shipped in a fraction of the time it would have taken solo.
If you’re a developer on the fence about AI-assisted development, my advice is simple: try it on a real project. Not a toy demo, but something you actually want to build and ship. That’s when you’ll truly understand the potential.
The future of development isn’t AI replacing developers. It’s developers who use AI outpacing those who don’t.
Have questions about this stack or how I used Claude? Reach out on X @smhnkmrtweet or check out the About page.