ヘッドレスCMSは、コンテンツ管理と表示を分離するアーキテクチャです。 マーケティングチームがCMSで記事を管理し、開発チームがフロントエンドのHTMLを制御できます。
しかし、分離されたアーキテクチャには「フロントエンドの実装次第でAIクローラがコンテンツを読めなくなる」というリスクがあります。 この記事では、ヘッドレスCMSとSSGフロントを組み合わせてGEO対策を実現する方法を解説します。
ヘッドレスCMSのGEO対策における構造的な課題
従来のCMS(WordPressなど)ではPHPがHTMLを生成し、AIクローラはそれを読めます。 ヘッドレスCMSではコンテンツはAPIで提供され、フロントエンドがHTMLを生成する必要があります。
従来型CMS
クローラ → サーバー → HTML(テキスト含む) → 読める
ヘッドレスCMS(CSR構成)
クローラ → サーバー → 空のHTML+JS → JS実行しない → 読めない
ヘッドレスCMS(SSG構成)
ビルド時にAPIからデータ取得 → HTMLを生成 → CDNに配置
クローラ → CDN → HTML(テキスト含む) → 読める
GEO対策の観点では、フロントエンドはSSGまたはSSRで構築することが前提です。
主要なヘッドレスCMSの比較
| CMS | 型 | API形式 | 日本語対応 | 構造化データ支援 |
|---|---|---|---|---|
| Contentful | SaaS | REST/GraphQL | 管理画面は英語 | なし(自前実装) |
| Strapi | OSS | REST/GraphQL | コミュニティ翻訳 | なし(自前実装) |
| microCMS | SaaS | REST | 日本語ネイティブ | なし(自前実装) |
| Sanity | SaaS | GROQ/GraphQL | 管理画面は英語 | なし(自前実装) |
構造化データ(JSON-LD)の生成は、どのCMSもフロントエンド側で実装する必要があります。
microCMS + AstroでのGEO対策実装
microCMSのAPIスキーマで、GEOに必要なフィールドを設計します。
descriptionを必須にしてメタディスクリプションの設定漏れを防ぎます。
// src/lib/microcms.ts
import { createClient, type MicroCMSListContent } from 'microcms-js-sdk';
const client = createClient({
serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
apiKey: import.meta.env.MICROCMS_API_KEY,
});
export interface Article extends MicroCMSListContent {
title: string;
description: string;
body: string;
category: { id: string; name: string };
tags?: { id: string; name: string }[];
author: { id: string; name: string };
}
export async function getAllArticles(): Promise<Article[]> {
const response = await client.getList<Article>({
endpoint: 'articles',
queries: { limit: 100, orders: '-publishedAt' },
});
return response.contents;
}
Astro側の記事ページテンプレートです。
---
// src/pages/blog/[slug].astro
import { getAllArticles } from '../../lib/microcms';
import StructuredData from '../../components/StructuredData.astro';
export async function getStaticPaths() {
const articles = await getAllArticles();
return articles.map((article) => ({
params: { slug: article.id },
props: { article },
}));
}
const { article } = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
---
<html lang="ja">
<head>
<title>{article.title}</title>
<meta name="description" content={article.description} />
<link rel="canonical" href={canonicalURL.href} />
<StructuredData
title={article.title}
description={article.description}
publishedAt={new Date(article.publishedAt!)}
author={{ name: article.author.name }}
url={canonicalURL.href}
/>
</head>
<body>
<article>
<h1>{article.title}</h1>
<div set:html={article.body} />
</article>
</body>
</html>
getStaticPathsでビルド時にmicroCMSから全記事を取得し、静的HTMLを生成します。
Contentful + Next.jsでの実装
// lib/contentful.ts
import { createClient } from 'contentful';
import { documentToHtmlString } from '@contentful/rich-text-html-renderer';
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
});
export async function getArticleBySlug(slug: string) {
const entries = await client.getEntries({
content_type: 'article',
'fields.slug': slug,
limit: 1,
});
if (!entries.items.length) return null;
const item = entries.items[0];
return {
...item.fields,
contentHtml: documentToHtmlString(item.fields.body as any),
};
}
Next.js側の実装パターンはmicroCMSと同じです。
generateStaticParamsでContentfulから全記事のslugを取得し、ビルド時にSSGで静的HTMLを生成します。
Webhookによるビルドトリガー
ヘッドレスCMS + SSG構成では、コンテンツ更新時にフロントエンドのビルドをトリガーする仕組みが必要です。
1. マーケティング担当者がCMSで記事を公開/更新
2. CMSがWebhookを送信
3. ホスティングサービスがビルドを実行
4. 新しい静的HTMLがCDNに配置される
microCMSの場合は管理画面の「API設定 → Webhook」で、VercelのDeploy Hookエンドポイントを設定します。
設定の注意点です。
- 記事の「公開」時だけでなく「更新」時もWebhookが飛ぶようにする
- 下書きの保存ではWebhookを飛ばさない(無駄なビルドを防ぐ)
- Webhookの失敗をSlack等に通知する設定を入れる
プレビュー環境のGEO対策
下書きの記事をプレビューする仕組みは必要ですが、プレビューURLがAIクローラにインデックスされないよう注意します。
// app/api/preview/route.ts(Next.js)
import { draftMode } from 'next/headers';
import { redirect } from 'next/navigation';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const slug = searchParams.get('slug');
const secret = searchParams.get('secret');
if (secret !== process.env.PREVIEW_SECRET) {
return new Response('Invalid token', { status: 401 });
}
draftMode().enable();
redirect(`/blog/${slug}`);
}
Draft Modeが有効な場合、generateMetadataでrobots: { index: false }を返し、AIクローラにインデックスされないようにしてください。
コンテンツモデルの設計指針
GEOに必要なフィールドをコンテンツモデルに含めます。
| フィールド | 用途 | 必須 |
|---|---|---|
| title | AI検索の引用タイトル | はい |
| description | AI検索の要約元 | はい |
| slug | 正規URLの生成 | はい |
| body | AIが読むメインコンテンツ | はい |
| author | E-E-A-T評価 | はい |
| publishedAt | 情報鮮度の判定 | はい |
| category / tags | サイトの専門性判定 | いいえ |
| ogImage | 視覚的な引用要素 | いいえ |
descriptionとauthorを必須にすることが重要です。
任意フィールドにすると、入力漏れでメタデータが欠落したページが公開されます。
実装チェックリスト
| 項目 | 確認内容 | 優先度 |
|---|---|---|
| レンダリング方式 | フロントエンドがSSGまたはSSRか | 高 |
| メタデータ | title、description、OGPが全ページに出力されているか | 高 |
| 構造化データ | ArticleスキーマがHTML内に出力されているか | 高 |
| Webhook | コンテンツ更新時にビルドがトリガーされるか | 高 |
| サイトマップ | 全記事がサイトマップに含まれているか | 高 |
| コンテンツモデル | description・authorが必須フィールドか | 中 |
| プレビュー | プレビューページにnoindexが設定されているか | 中 |
まとめ——ヘッドレスCMSのGEO対策はフロントエンドで決まる
ヘッドレスCMSそのものにはGEO対策の機能はありません。 GEO対策の質は、フロントエンドのレンダリング方式と実装で決まります。
SSGフロントと組み合わせれば、AIクローラが確実に読めるHTMLを生成できます。 マーケティングチームとエンジニアの分業が機能する構成を作ることが目標です。