Whipping Up a Blog with Claude Code (Part 5: OG Images, SEO, Analytics)
Do the whole Medium Priority list. Automatic OG images, JSON-LD, Analytics, Callouts, and a reading progress bar.
Whipping Up a Blog with Claude Code series
(10 parts)- 1Whipping Up a Blog with Claude Code (Part 1: Design)
- 2Whipping Up a Blog with Claude Code (Part 2: Customizing)
- 3Whipping Up a Blog with Claude Code (Part 3: The AI Writing System)
- 4Whipping Up a Blog with Claude Code (Part 4: Search, RSS, TOC, Draft)
- 5Whipping Up a Blog with Claude Code (Part 5: OG Images, SEO, Analytics)
- 6Whipping Up a Blog with Claude Code (Part 6: Comments, Tags, UX)
- 7Whipping Up a Blog with Claude Code (Part 7: Popular Post Rankings and Analytics)
- 8Whipping Up a Blog with Claude Code (Part 8: A Mascot Character)
- 9Whipping Up a Blog with Claude Code (Part 9: Dropping Categories for Tags)
- 10Whipping Up a Blog with Claude Code (Part 10: Pinning the Persona Update as a Skill)
Just do all of it
Part 4 left me with roadmap.md. Five Medium Priority items were still open.
So I said:
work through @roadmap.mdMeaning: do all of it.
It created five tasks and started working through them in order.
1. Automatic OG image generation
The thumbnail that shows up when you share on social. With @vercel/og you can generate one dynamically in an Edge Function.
// src/app/api/og/route.tsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
const title = searchParams.get('title') || '๋ ์ฉ๋ ํ๋ณด์ฉ';
const category = searchParams.get('category') || '';
return new ImageResponse(
<div style={{ /* styles */ }}>
{category && <span>{category}</span>}
<div>{title}</div>
<span>๋ ์ฉ๋ ํ๋ณด์ฉ</span>
</div>,
{ width: 1200, height: 630 }
);
}Wire it into the metadata and you're done.
const ogImageUrl = `${SITE_URL}/api/og?title=${encodeURIComponent(post.title)}&category=${encodeURIComponent(category)}`;
return {
openGraph: {
images: [{ url: ogImageUrl, width: 1200, height: 630 }],
},
};Now hitting /api/og?title=test returns an image.
2. JSON-LD schema
Markup that lets Google understand the structure of a post.
// src/components/JsonLd.tsx
export function ArticleJsonLd({ title, description, publishedTime, url, category, tags }) {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: title,
datePublished: publishedTime,
author: { '@type': 'Person', name: '์์์' },
// ...
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
);
}Drop it into the post page.
<ArticleJsonLd
title={post.title}
description={post.description}
publishedTime={post.date}
url={url}
category={category}
tags={post.tags}
/>3. Vercel Analytics
The simplest one.
npm install @vercel/analyticsOne line into layout.tsx.
import { Analytics } from "@vercel/analytics/next";
// inside body
<Analytics />Deploy to Vercel and it shows up in the dashboard right away.
4. Callout component
The box you use to highlight something in docs. NOTE, WARNING, TIP, that kind of thing.
// src/components/Callout.tsx
type CalloutType = 'note' | 'warning' | 'tip' | 'danger' | 'info';
export default function Callout({ type = 'note', title, children }) {
const config = calloutConfig[type]; // per-type color and icon
return (
<div className={`${config.bgClass} ${config.borderClass}`}>
<span>{config.icon} {title}</span>
<div>{children}</div>
</div>
);
}In MDX it looks like this:
<Callout type="tip" title="Tip">
Use it like this.
</Callout>
<Callout type="warning">
You can note caveats too.
</Callout>Five types: note, warning, tip, danger, info
5. Reading progress indicator
The bar at the top that fills as you scroll.
// src/components/ReadingProgress.tsx
'use client';
export default function ReadingProgress() {
const [progress, setProgress] = useState(0);
useEffect(() => {
const updateProgress = () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
setProgress((scrollTop / docHeight) * 100);
};
window.addEventListener('scroll', updateProgress, { passive: true });
return () => window.removeEventListener('scroll', updateProgress);
}, []);
return (
<div className="fixed top-0 left-0 right-0 h-1">
<div style={{ width: `${progress}%` }} />
</div>
);
}Put it on the post page and that's it.
Build check
Ran the build after everything.
โ Compiled successfully
โ Generating static pages (14/14)No problems.
roadmap update
Claude Code updated roadmap.md on its own.
## โ
Done
### 2026-02-04
- [x] Automatic OG image generation
- [x] JSON-LD schema
- [x] Vercel Analytics
- [x] Callout component
- [x] Reading progress indicatorMedium Priority is empty. Only Low Priority left.
Part 5 summary
What I did this time:
- OG images โ generated dynamically with
@vercel/og - JSON-LD โ structured data for SEO
- Analytics โ Vercel Analytics wired up
- Callout โ highlight component for MDX
- Reading progress bar โ scroll-based progress
Five features from a single "do all of it."
Low Priority is comments (Giscus), a tag archive, and a Back to Top button. If I need them I can probably just say "do all of it" again.