ヘッドレス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の比較

CMSAPI形式日本語対応構造化データ支援
ContentfulSaaSREST/GraphQL管理画面は英語なし(自前実装)
StrapiOSSREST/GraphQLコミュニティ翻訳なし(自前実装)
microCMSSaaSREST日本語ネイティブなし(自前実装)
SanitySaaSGROQ/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エンドポイントを設定します。

設定の注意点です。

プレビュー環境の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が有効な場合、generateMetadatarobots: { index: false }を返し、AIクローラにインデックスされないようにしてください。

コンテンツモデルの設計指針

GEOに必要なフィールドをコンテンツモデルに含めます。

フィールド用途必須
titleAI検索の引用タイトルはい
descriptionAI検索の要約元はい
slug正規URLの生成はい
bodyAIが読むメインコンテンツはい
authorE-E-A-T評価はい
publishedAt情報鮮度の判定はい
category / tagsサイトの専門性判定いいえ
ogImage視覚的な引用要素いいえ

descriptionauthorを必須にすることが重要です。 任意フィールドにすると、入力漏れでメタデータが欠落したページが公開されます。

実装チェックリスト

項目確認内容優先度
レンダリング方式フロントエンドがSSGまたはSSRか
メタデータtitle、description、OGPが全ページに出力されているか
構造化データArticleスキーマがHTML内に出力されているか
Webhookコンテンツ更新時にビルドがトリガーされるか
サイトマップ全記事がサイトマップに含まれているか
コンテンツモデルdescription・authorが必須フィールドか
プレビュープレビューページにnoindexが設定されているか

まとめ——ヘッドレスCMSのGEO対策はフロントエンドで決まる

ヘッドレスCMSそのものにはGEO対策の機能はありません。 GEO対策の質は、フロントエンドのレンダリング方式と実装で決まります。

SSGフロントと組み合わせれば、AIクローラが確実に読めるHTMLを生成できます。 マーケティングチームとエンジニアの分業が機能する構成を作ることが目標です。