Use Cases

Cara Membangun Blog CMS dengan Claude Code

Pelajari cara membangun blog CMS menggunakan Claude Code. Dilengkapi contoh kode praktis dan panduan langkah demi langkah.

Membangun Blog CMS dengan Claude Code

Memiliki blog CMS sendiri memberikan kontrol penuh atas kebebasan desain, kontrol SEO, dan optimasi performa. Dengan Claude Code, CMS yang dilengkapi manajemen artikel, preview, dan alur kerja penerbitan bisa dibangun dalam waktu singkat.

Desain Prompt

> Buat blog CMS dengan Next.js App Router.
> Bangun panel admin yang bisa membuat, edit, dan menerbitkan artikel Markdown,
> serta frontend yang di-generate dengan SSG.
> Implementasikan juga filtering berdasarkan kategori dan tag.

Definisi Data Model Artikel

// src/types/post.ts
export interface Post {
  id: string;
  title: string;
  slug: string;
  content: string;        // Markdown
  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';
}

Implementasi API Manajemen Artikel

// 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 });
}

Component Markdown Editor

// 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' : ''}`}
        >
          Preview
        </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="Tulis artikel dalam Markdown..."
        />
      )}
      <div className="flex justify-end p-3 border-t">
        <button
          onClick={() => onSave(content)}
          className="bg-blue-600 text-white px-6 py-2 rounded"
        >
          Simpan
        </button>
      </div>
    </div>
  );
}

Auto-Generate Metadata SEO

Dengan meminta Claude Code, auto-generate gambar OGP dari konten artikel dan pengaturan meta tag juga bisa diimplementasikan.

// 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 }] : [],
    },
  };
}

Artikel Terkait

Untuk bagian frontend pembangunan CMS, Panduan Perbandingan SSR/SSG sangat membantu. Untuk langkah-langkah SEO, lihat juga Optimasi SEO.

Untuk pemilihan library pemrosesan Markdown, ekosistem unified.js cukup komprehensif dan berguna sebagai referensi.

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