ブログCMSは自作かヘッドレスか。AstroとClaude Codeで僕が出した結論
ブログCMSを自作するかmicroCMS/Contentfulに頼るか。記事モデル設計・MDX運用・SSG/ISRの判断を、Astroで自サイトを回す僕の実体験とコードで整理する。
「ブログCMSくらい、サクッと作れるでしょ」
そう思って週末に手をつけた自作CMSは、3週間後、記事30本を抱えたまま管理画面のログインがバグって動かなくなりました。記事データはDBの中。バックアップは取ってない。冷や汗をかきながらSQLを叩いて、深夜に全記事をMarkdownへ書き出したのを今でも覚えています。
あのとき学んだのは、ブログCMSの本当の難所は「記事を書く画面」じゃないということ。記事データをどう持つか、壊れたときどう戻すか、増えたとき誰が面倒を見るか。ここを最初に決めないと、あとで必ず詰みます。
今このサイト(claudecode-lab.com)はAstroで動いていて、CMSは「自前のファイルベース」です。WordPressでもmicroCMSでもありません。なぜその選択にしたのか、自作とヘッドレスCMSをどう天秤にかけたのか、Claude Codeをどこまで運用に組み込んでいるのか。実際に回してみてわかったことを、判断材料ごと全部書きます。
この記事の要点
- ブログCMSは「編集画面」より先に**記事モデル(どんな項目を持つか)**を決める。ここが土台。
- 選択肢は大きく3つ。自前ファイルベース(Markdown/MDX)/ヘッドレスCMS(microCMS・Contentful等)/WordPress系。個人〜小規模なら自前が一番壊れにくい。
- 自前の弱点は「編集者がエンジニアじゃないと辛い」「画像管理が雑になる」。ここが効くならヘッドレスCMS。
- AstroならContent Collectionsで記事をスキーマ(型の約束)で守れるので、壊れた記事をビルド前に止められる。
- Claude Codeは「記事を書くAI」ではなく**「公開前チェックを回す作業者」**として使うと事故が減る。コピペで動く検証スクリプトを後半に置いた。
そもそもCMSって、管理画面のことじゃない
CMSと聞くと、多くの人がWordPressのログイン画面を思い浮かべます。記事を打ち込む、画像をアップする、公開ボタンを押す。あの画面。
でもCMSの正体はContent Management System、つまり**「記事を決まった形でしまって、編集して、確認して、公開する仕組み」全体**です。立派な管理画面はその一部にすぎません。極端な話、記事がきれいな形で保管されていて、安全に公開できるなら、管理画面はテキストエディタでもいいんです。
ここを誤解すると、「まず管理画面を作らなきゃ」と思って、認証もDBもプレビューも一気に抱え込み、僕みたいに自滅します。順番が逆なんですね。先に決めるべきは、記事1本がどんなデータでできているかです。
まず記事モデルを設計する(ここが9割)
記事を「タイトルと本文」だけだと思っていると、運用が始まった瞬間に破綻します。実際に必要なのは、本文の周りにくっつく細かい情報です。これを記事モデル(データの設計図)と呼びます。
僕がこのサイトで実際に使っている項目を、役割つきで並べます。
| 項目 | 何のため | 例 |
|---|---|---|
title | 一覧・検索結果・OGPに出る顔 | ”ブログCMSは自作か…” |
description | 検索結果の説明文(120字以内) | “ブログCMSを自作するか…” |
pubDate | 公開日。並び順の基準 | 2025-12-22 |
updatedDate | 更新日。最新性を読者に約束する | 2026-06-07 |
category | 記事の大分類。決まった候補から選ぶ | use-cases |
tags | 検索・関連付け用のキーワード | [“Astro”, “MDX”] |
heroImage | 一覧とOGPカードの画像 | /images/hero/… |
lang | 多言語運用の言語コード | ja |
ポイントは2つ。ひとつは、categoryのように「決まった候補から選ぶ」項目を作ること。自由入力にすると「Tips」「tips」「ヒント」が混在して、あとで一覧ページが破綻します。
もうひとつは、収益化やSEOに効く項目を最初から枠として持っておくこと。あとから全記事に項目を足すのは地獄なので、descriptionやupdatedDateは最初から必須にしておきます。記事から教材や相談につなげたいなら、CTAのラベルとURLを持つ枠を足すのもありです。
自作 vs ヘッドレスCMS、どっちにすべきか
ここが一番聞かれるところです。先に答えを書くと、個人〜小規模で、書く人がコードを触れるなら自前ファイルベースが一番ラク。それ以外の事情が強ければヘッドレスCMSを足す、という順で考えます。
| 観点 | 自前(Markdown/MDX) | ヘッドレスCMS(microCMS/Contentful等) | WordPress系 |
|---|---|---|---|
| 初期コスト | ほぼゼロ | 無料枠あり、規模で課金 | サーバー代+運用 |
| 記事の置き場 | Gitリポジトリ(=自動でバックアップ) | 外部サービスのDB | 自前DB |
| 壊れたとき | git revertで一発 | サービス障害は手が出ない | 自分で復旧 |
| 非エンジニアの編集 | 辛い(Markdown必須) | 得意(管理画面で完結) | 得意 |
| 画像管理 | 自分で頑張る | CDN込みで楽 | プラグイン頼み |
| 多言語 | フォルダ運用で自前制御 | サービスの機能に依存 | プラグイン依存 |
| ベンダーロックイン | なし | あり(移行が重い) | 中 |
僕がこのサイトで自前を選んだ理由は単純で、**「記事がGitに入っている安心感」**です。冒頭の事故以来、記事データが自分の手元のテキストとして残ること、git revert一発で前の状態に戻せることが、何より効きました。サービスが落ちても僕の記事は消えません。
逆に、もし編集メンバーにエンジニアじゃない人が複数いたら、迷わずmicroCMSのようなヘッドレスCMSを使います。Markdownを書け、Gitを使え、というのは普通の編集者には酷だからです。自前は「書く人=コードを触れる人」のときだけ強い。ここを見誤ると、結局誰も更新しないブログが完成します。
判断軸はこの3つで足ります。
- 書く人はコードを触れるか → 触れないならヘッドレスCMS寄り。
- 記事を自分の管理下に置きたいか → ロックインを嫌うなら自前寄り。
- 画像・メディアが多いか → 多いならCDN込みのヘッドレスCMSが楽。
SSGとISR、ブログならどっちを選ぶか
CMSの裏側で必ず出てくるのが、ページの作り方です。専門用語が2つ出てくるので、先に身近な言葉に直します。
- SSG(静的サイト生成):公開のたびに全ページを「作り置き」しておく方式。お弁当を朝まとめて作るイメージ。表示は爆速、サーバーは不要。
- ISR(増分的静的再生成):基本は作り置きだけど、古くなったページだけこっそり作り直す方式。Next.jsなどが持つ仕組み。
ブログCMSなら、まずSSGで十分です。記事は一度書いたらそう頻繁に中身が変わらないので、作り置きが一番相性がいい。このサイトもSSGで、ビルドして静的ファイルを配るだけ。だからサーバー障害という概念がほぼなく、表示も速いです。
ISRが効くのは、「在庫数」「コメント数」みたいにページ内に頻繁に変わる数字があるときや、記事数が数万本あってフルビルドが重すぎるときです。個人ブログでそこまで行くことは滅多にないので、僕は「SSGで始めて、本当に困ったらISR」を勧めます。レンダリング方式の選び方はSSR vs SSG比較: Claude Codeで選ぶNext.js/Astroの最適レンダリングで実装例つきに掘り下げています。
Astroで記事を「型」で守る
ここから具体です。Astroには Content Collections という仕組みがあって、記事のfrontmatter(記事冒頭のメタ情報)を**型(このデータは文字列、ここは日付、この値は決まった候補だけ、という約束)**で縛れます。
何が嬉しいかというと、約束を破った記事はビルドが通らないんです。「descriptionを書き忘れた」「categoryに存在しない値を入れた」みたいなミスが、公開前に赤字で止まる。人間のレビューより先に、機械が弾いてくれます。
次のコードは Astro 5系の src/content.config.ts としてそのまま動く形です。実際のこのサイトの設定をベースに、品質ゲートを少し厳しめにしています。
// src/content.config.ts
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
// 記事1本の「型の約束」。ここを破った記事はビルドで止まる
const blogSchema = z.object({
title: z.string().min(15).max(80), // 短すぎ・長すぎを弾く
description: z.string().min(40).max(120), // 検索結果で途切れない長さ
pubDate: z.coerce.date(), // 文字列の日付を自動でDate化
updatedDate: z.coerce.date().optional(),
category: z.enum([ // 決まった候補からしか選べない
"getting-started",
"tips-and-tricks",
"use-cases",
"comparison",
"advanced",
]),
tags: z.array(z.string()).default([]),
heroImage: z.string().startsWith("/images/").optional(),
draft: z.boolean().default(false),
lang: z
.enum(["ja", "en", "zh", "ko", "es", "fr", "de", "pt", "hi", "id"])
.default("ja"),
});
// 言語ごとにフォルダを分けて、同じスキーマで縛る
const makeBlog = (base: string) =>
defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base }),
schema: blogSchema,
});
export const collections = {
blog: makeBlog("./src/content/blog"),
"blog-en": makeBlog("./src/content/blog-en"),
// 必要な言語ぶんだけ並べる
};
description を120字以内に縛っているのは、検索結果やSNSカードで途中切れさせないためです。短いと魅力が伝わらず、長いと大事な語が見切れる。機械的に上限を決めておくと、毎回悩まずに済みます。Astroでの土台づくりはClaude CodeでAstroサイトを作る実践ガイド|Content CollectionsとSSG運用に手順をまとめてあります。
Markdownか、MDXか
記事の中身を書く形式も決めどころです。これも身近に言うと、MDXは**「Markdownに部品(コンポーネント)を混ぜられる版」**です。
普段の本文はMarkdownで十分です。見出し、リスト、コードブロック、表。これで記事の9割は書けます。じゃあMDXは何のためかというと、本文の中にCTAボタンや比較表のような「動く部品」を埋め込みたいときだけ。
僕の運用ルールはシンプルで、「迷ったらMarkdown、部品が要る記事だけMDX」。最初から全部MDXにすると、ただの文章記事にまで余計な依存が乗って重くなります。MDXのfrontmatterはこんな具合です。
---
title: "ブログCMSは自作かヘッドレスか"
description: "AstroとClaude CodeでブログCMSを設計する判断材料を整理する。"
pubDate: "2025-12-22"
updatedDate: "2026-06-07"
category: "use-cases"
tags: ["Astro", "MDX"]
heroImage: "/images/hero/hero-036.png"
lang: "ja"
---
## 最初の見出し
本文はMarkdownで書く。CTAや表など部品が必要なところだけMDXの力を借りる。
ひとつだけ注意。frontmatterに本文の結論を書きすぎないこと。descriptionは検索結果で読者を誘う短い文、本文の冒頭は「なぜ今これを読むべきか」を伝える場所。役割が違います。
Claude Codeは「公開前の門番」に使う
ここでようやくClaude Codeの出番です。多くの人が「記事を書かせるAI」として使いますが、僕の実感ではいちばん効くのは公開前チェックを回す作業者としての使い方です。
書かせるだけだと、薄い記事や似たネタを量産しがちです。でも「公開していいか」の判断は、検査可能な条件に分解できる。descriptionが120字を超えてないか、updatedDateがあるか、内部リンクと公式リンクが入っているか、コードが疑似コードのままじゃないか。こういう機械でわかる門番を持たせると、感覚じゃなくチェック結果で公開を決められます。
下のスクリプトは依存パッケージなしで動きます。scripts/validate-blog.mjs として保存し、node scripts/validate-blog.mjs claude-code-blog-cms のように実行します。frontmatterの最低品質、内部リンク、公式リンク、コードブロックの有無を見ます。
// scripts/validate-blog.mjs
import fs from "node:fs";
import path from "node:path";
const slug = process.argv[2];
if (!slug) {
console.error("使い方: node scripts/validate-blog.mjs <slug>");
process.exit(1);
}
const file = path.join(process.cwd(), "src", "content", "blog", `${slug}.mdx`);
if (!fs.existsSync(file)) {
console.error(`記事が見つかりません: ${file}`);
process.exit(1);
}
const source = fs.readFileSync(file, "utf8");
// frontmatter(先頭の --- に挟まれた部分)と本文を分ける
const fm = source.match(/^---\n([\s\S]*?)\n---/);
const body = source.replace(/^---\n[\s\S]*?\n---/, "");
// frontmatterを「キー: 値」のゆるいパースで読む(軽いゲート用途)
const meta = Object.fromEntries(
(fm ? fm[1].split("\n") : []).flatMap((line) => {
const i = line.indexOf(":");
if (i === -1) return [];
const key = line.slice(0, i).trim();
const value = line.slice(i + 1).trim().replace(/^"|"$/g, "");
return [[key, value]];
})
);
const failures = [];
if (!meta.updatedDate) failures.push("updatedDate がありません");
if ((meta.description || "").length > 120) {
failures.push("description が120字を超えています");
}
if (!/heroImage:\s*"?\/images\//.test(source)) {
failures.push("heroImage(/images/ 配下)がありません");
}
if (!/https:\/\/docs\.astro\.build/.test(source)) {
failures.push("Astro公式ドキュメントへのリンクがありません");
}
if (!/\]\(\/blog\/[a-z0-9-]+\/\)/.test(source)) {
failures.push("内部リンク(/blog/...)がありません");
}
// コードフェンスは必ず偶数(開いたら閉じる)であるべき
const fence = "`".repeat(3); // バッククォート3つ
if ((body.match(new RegExp(fence, "g")) || []).length % 2 !== 0) {
failures.push("コードブロックの開閉が合っていません");
}
if (failures.length) {
console.error(failures.map((f) => `- ${f}`).join("\n"));
process.exit(1);
}
console.log(`OK: ${slug} は公開前チェックを通過しました。`);
完璧なYAMLパーサーではありませんが、公開前の軽いゲートとしては十分です。本格運用では gray-matter やAstroのビルドチェックに寄せると、配列や複雑な値も安全に扱えます。Claude Codeにはこのスクリプトを実行させ、落ちた項目だけ直させる運用にしています。
Claude Codeへの依頼文も、「いい記事にして」ではなく検査可能な条件に分解するのがコツです。触ってよいファイル、updatedDateの値、文字数の上限、必要なリンク、実行するチェックコマンドまで明記する。曖昧な品質より、具体条件のほうがAIは圧倒的に強いです。
配信面(RSS・サイトマップ・OGP)をCMSの外に置かない
記事ファイルが正しくても、RSSに出ない、サイトマップに載らない、OGP画像が空だと、公開した価値が半減します。ここはCMS設計の「最後の一歩」なのに、忘れられがちです。
- 一覧ページに出るタイトルと
description - OGPカードで使う
heroImage - RSSに含める最新記事
- サイトマップに含める全公開URL
この4つを、記事の公開チェックと同じ場所で見るようにします。そろって初めて「ファイルを置いた」ではなく「読者に届く状態になった」と言えます。RSSとサイトマップは事故が静かに起きやすい領域なので、RSSフィードが壊れて誰も気づかない事故をClaude Codeで防ぐとサイトマップを毎ビルド今日付にして順位を落とした話とClaude Code自動生成に、僕がやらかした実例つきでまとめてあります。
公式ドキュメントも必ず当たってください。AstroのContent Collectionsは記事データの型管理、MDX integrationはMarkdown内で部品を使う仕組みの一次情報です。
僕がやらかした失敗3つ
正直に書きます。今の構成にたどり着くまで、わりと転びました。
ひとつ目は冒頭の通り、いきなり管理画面とDBから作ったこと。記事モデルも決めず編集UIを先に作り、バックアップもなく、ログインがバグった瞬間に詰みました。今は「Gitに入るテキスト=それ自体がバックアップ」の自前ファイルベースにして、夜中に冷や汗をかくことはなくなりました。
ふたつ目は、categoryを自由入力にしたこと。「Tips」「tips」「小技」が乱立して、一覧ページのフィルタが意味をなさなくなりました。z.enum()で候補を固定してからは、表記ゆれが構造的に起きません。
みっつ目は、updatedDateを機械的に全部今日付にしたこと。サイトマップが「全記事きょう更新」になり、検索エンジンの信頼を一時的に落としました。日付は読者と検索エンジンへの約束です。中身を本当に確認したときだけ更新する、と運用を改めました。
よくある質問
Q. 結局、自作CMSとmicroCMSのようなヘッドレスCMS、どっちがいいんですか? A. 書く人がコードを触れて、記事を自分の管理下に置きたいなら自前ファイルベース。非エンジニアの編集者が複数いる、画像が多い、管理画面が必須、ならヘッドレスCMSです。判断軸は「書く人のスキル」「ロックインの許容度」「メディア量」の3つで足ります。
Q. WordPressじゃダメなんですか? A. ダメではありません。プラグイン資産と編集体験は強力です。ただサーバー運用・更新・セキュリティの面倒が常につきまといます。表示速度と保守の軽さ、記事のGit管理を重視するなら、AstroのようなSSG+ファイルベースが軽量です。
Q. ブログにISRは要りますか? A. たいてい要りません。記事は頻繁に中身が変わらないのでSSG(作り置き)が最適です。ページ内に頻繁に変わる数字がある、記事が数万本でフルビルドが重い、といった事情が出てきて初めてISRを検討すれば十分です。
Q. Claude Codeに記事を全部書かせていいですか? A. 下書きは任せていいですが、丸投げは薄い記事を量産します。僕は「書かせる」より「公開前チェックを回させる」使い方を勧めます。本記事のスクリプトのように、検査可能な条件を持たせると事故が減ります。
Q. MarkdownとMDX、最初はどっち? A. Markdownで始めてください。CTAボタンや動く比較表など、本文に部品を埋めたい記事が出てきたら、その記事だけMDXにします。最初から全部MDXにすると不要な依存で重くなります。
実際に試した結果
このサイトを自前ファイルベースのAstro CMSで1年近く回してみて、いちばん効いたのは派手な機能ではなく**「記事がGitに入っている」という安心感**でした。事故ったらrevertで戻る。サービスが落ちても記事は消えない。スキーマが壊れた記事をビルド前に止めてくれる。地味ですが、夜中の冷や汗がなくなったのが一番の成果です。
一方で、限界もはっきりしました。画像の管理は今も手作業寄りで雑になりがちだし、もし非エンジニアの編集者が増えたら、この構成は確実に回らなくなります。そのときは迷わずヘッドレスCMSを足すつもりです。「正解のCMS」はなく、書く人と規模に合った構成があるだけ、というのが1年回しての実感です。
ClaudeCodeLabでは、ブログCMSのスキーマ設計、多言語運用、公開前QAゲート、記事から教材・相談への導線設計まで扱っています。手を動かせるテンプレートが欲しい方は教材一覧をのぞいてみてください。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
制作会社がClaude Codeに触らせる前に決める権限チェックリスト
クライアントサイトを壊さずにAI編集を使うための、制作会社向け権限と確認の型です。
SaaSサポートのバグ報告をClaude Codeで再現手順に変える実務フロー
問い合わせ文をそのまま開発へ投げず、再現手順、証拠、次の一手に整えるサポート向け手順です。
Obsidianの古いメモをClaude Codeの指示書に変える10分ルーチン
Obsidianに溜めたメモが毎回ゴミになる人へ。事実・決定・未確認に仕分けして、Claude Codeがそのまま動ける指示書に変える朝の10分の型を紹介します。