Use Cases

How to Build a Blog CMS with Claude Code

Learn how to build a blog cms using Claude Code. Includes practical code examples and step-by-step guidance.

ブログCMSをClaude Codeで構築する

自前のブログCMSを持つことで、デザインの自由度、SEO制御、パフォーマンス最適化を完全にコントロールできます。Claude Codeを使えば、記事管理・プレビュー・公開ワークフローまで備えたCMSを短期間で構築できます。

プロンプト設計

> Next.js App RouterでブログCMSを作って。
> マークダウン記事の作成・Edit・公開ができる管理画面と、
> SSGで生成されるフロントエンドの両方を構築して。
> カテゴリとタグによるフィルタリングも実装して。

記事データモデルの定義

// src/types/post.ts
export interface Post {
  id: string;
  title: string;
  slug: string;
  content: string;        // マークダウン
  excerpt: string;
  coverImage?: string;
  category: string;
  tags: string[];
  status: 'draft' | 'published' | 'archived';
  publishedAt?: Date;
  createdAt: Date;
  updatedAt: Date;
  authorId: string;
}

export interface PostCreateInput {
  title: string;
  content: string;
  category: string;
  tags: string[];
  status?: 'draft' | 'published';
}

記事管理APIの実装

// src/app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { generateSlug } from '@/lib/utils';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const category = searchParams.get('category');
  const tag = searchParams.get('tag');
  const status = searchParams.get('status') || 'published';

  const posts = await prisma.post.findMany({
    where: {
      status,
      ...(category && { category }),
      ...(tag && { tags: { has: tag } }),
    },
    orderBy: { publishedAt: 'desc' },
    select: {
      id: true,
      title: true,
      slug: true,
      excerpt: true,
      category: true,
      tags: true,
      publishedAt: true,
      coverImage: true,
    },
  });

  return NextResponse.json(posts);
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  const slug = generateSlug(body.title);

  const post = await prisma.post.create({
    data: {
      ...body,
      slug,
      excerpt: body.content.substring(0, 160),
      publishedAt: body.status === 'published' ? new Date() : null,
    },
  });

  return NextResponse.json(post, { status: 201 });
}

マークダウンエディタコンポーネント

// src/components/admin/MarkdownEditor.tsx
'use client';
import { useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

interface Props {
  initialContent?: string;
  onSave: (content: string) => void;
}

export function MarkdownEditor({ initialContent = '', onSave }: Props) {
  const [content, setContent] = useState(initialContent);
  const [isPreview, setIsPreview] = useState(false);

  return (
    <div className="border rounded-lg">
      <div className="flex border-b">
        <button
          onClick={() => setIsPreview(false)}
          className={`px-4 py-2 ${!isPreview ? 'bg-blue-50 font-bold' : ''}`}
        >
          Edit
        </button>
        <button
          onClick={() => setIsPreview(true)}
          className={`px-4 py-2 ${isPreview ? 'bg-blue-50 font-bold' : ''}`}
        >
          プレビュー
        </button>
      </div>
      {isPreview ? (
        <div className="prose p-4 max-w-none">
          <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
        </div>
      ) : (
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
          className="w-full h-96 p-4 font-mono text-sm resize-none"
          placeholder="マークダウンで記事を書く..."
        />
      )}
      <div className="flex justify-end p-3 border-t">
        <button
          onClick={() => onSave(content)}
          className="bg-blue-600 text-white px-6 py-2 rounded"
        >
          保存
        </button>
      </div>
    </div>
  );
}

SEOメタデータの自動生成

Claude Codeに依頼すれば、記事の内容からOGP画像の自動生成やメタタグの設定も実装できます。

// src/lib/seo.ts
export function generatePostMeta(post: Post) {
  return {
    title: `${post.title} | MyBlog`,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      publishedTime: post.publishedAt?.toISOString(),
      tags: post.tags,
      images: post.coverImage ? [{ url: post.coverImage }] : [],
    },
  };
}

関連記事

CMS構築のフロントエンド部分にはSSR/SSGの比較ガイドが役立ちます。SEO対策についてはSEO最適化も併せてご確認ください。

マークダウン処理のライブラリ選定には、unified.jsのエコシステムが充実しており参考になります。

#Claude Code #CMS #blog #Next.js #TypeScript