製品情報、料金表、事例一覧など、APIからデータを取得して表示する動的ページは多くのWebサイトに存在します。 これらのページは、JavaScriptがAPIを呼び出して描画するケースが多く、AIクローラから見ると「空のページ」になりがちです。
この記事では、API駆動のコンテンツをAI検索に対応させる方法を、SSR化・プリレンダリング・動的OGP生成の3つの手法で解説します。
API駆動ページがAIクローラに読まれない理由
クライアントサイドでAPIを呼ぶ一般的なパターンを見ます。
function ProductPage() {
const [product, setProduct] = useState(null);
useEffect(() => {
fetch('/api/products/123')
.then((res) => res.json())
.then((data) => setProduct(data));
}, []);
if (!product) return <div>Loading...</div>;
return <h1>{product.name}</h1>;
}
AIクローラがこのページにアクセスした場合、JavaScriptを実行しないため、useEffectのAPI呼び出しは実行されません。
クローラが読めるのは「Loading…」という文字列だけです。
解決策の全体像
| 手法 | 動作 | 適したケース |
|---|---|---|
| SSG | ビルド時にAPIを呼び、静的HTMLを生成 | 更新頻度が低いデータ |
| ISR | SSG+定期的なバックグラウンド再生成 | 定期更新データ |
| SSR | リクエスト時にサーバーでAPI呼び出し | リアルタイムデータ |
SSRでAPI駆動ページをAI対応にする
Next.jsのApp Routerでの実装です。
// app/products/[id]/page.tsx
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
interface Product {
id: string;
name: string;
description: string;
price: number;
category: string;
features: string[];
}
async function getProduct(id: string): Promise<Product | null> {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 60 },
});
if (!res.ok) return null;
return res.json();
}
export async function generateMetadata({
params,
}: {
params: { id: string };
}): Promise<Metadata> {
const product = await getProduct(params.id);
if (!product) return { title: '製品が見つかりません' };
return {
title: `${product.name} | 製品情報`,
description: product.description.slice(0, 160),
alternates: {
canonical: `https://example.com/products/${params.id}`,
},
};
}
export default async function ProductPage({
params,
}: {
params: { id: string };
}) {
const product = await getProduct(params.id);
if (!product) notFound();
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
description: product.description,
offers: {
'@type': 'Offer',
price: product.price,
priceCurrency: 'JPY',
},
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>
<h1>{product.name}</h1>
<p>{product.description}</p>
<dl>
<dt>価格</dt>
<dd>{product.price.toLocaleString()}円</dd>
</dl>
<h2>特徴</h2>
<ul>
{product.features.map((f, i) => (
<li key={i}>{f}</li>
))}
</ul>
</article>
</>
);
}
next: { revalidate: 60 }により、60秒間キャッシュしてバックグラウンドで再検証します。
AIクローラにとって、キャッシュ済みのページは即座にHTMLが返されるため、タイムアウトのリスクが低減します。
プリレンダリング(SSG)でAPI駆動ページを静的化する
事例紹介や製品カタログなど、更新頻度が低いコンテンツはSSGが最も確実です。
// app/cases/[slug]/page.tsx
export async function generateStaticParams() {
const cases = await fetch('https://api.example.com/cases').then((r) => r.json());
return cases.map((c: any) => ({ slug: c.slug }));
}
export default async function CaseStudyPage({
params,
}: {
params: { slug: string };
}) {
const caseStudy = await fetch(
`https://api.example.com/cases/${params.slug}`
).then((r) => r.json());
return (
<article>
<h1>{caseStudy.title}</h1>
<p>{caseStudy.description}</p>
<h2>導入成果</h2>
<table>
<thead><tr><th>指標</th><th>結果</th></tr></thead>
<tbody>
{caseStudy.results.map((r: any, i: number) => (
<tr key={i}><td>{r.metric}</td><td>{r.value}</td></tr>
))}
</tbody>
</table>
</article>
);
}
CDNから配信されるため、応答速度は最速で、APIの状態にも依存しません。
オンデマンドISR
APIデータ更新時に即座にHTMLを再生成したい場合、オンデマンドISRを使います。
// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
export async function POST(request: Request) {
const secret = request.headers.get('x-revalidate-secret');
if (secret !== process.env.REVALIDATE_SECRET) {
return Response.json({ error: 'Invalid secret' }, { status: 401 });
}
const { path } = await request.json();
revalidatePath(path);
return Response.json({ revalidated: true, path });
}
# 製品ページの再生成をトリガー
curl -X POST https://example.com/api/revalidate \
-H "Content-Type: application/json" \
-H "x-revalidate-secret: your-secret-key" \
-d '{"path": "/products/123"}'
APIバックエンドからデータ更新時にこのエンドポイントを呼べば、定期再生成を待たずにHTMLが最新化されます。
デプロイ後の検証
# HTMLにコンテンツが含まれるか
curl -A "GPTBot" https://example.com/products/123 | head -50
# JSON-LDが出力されているか
curl -s https://example.com/products/123 \
| grep -o '<script type="application/ld+json">.*</script>'
# レスポンス時間(5秒以内が目安)
curl -o /dev/null -s -w "Total: %{time_total}s\n" \
-A "GPTBot" https://example.com/products/123
実装チェックリスト
| 項目 | 確認内容 | 優先度 |
|---|---|---|
| レンダリング | AIに読ませたいページがSSGまたはSSRか | 高 |
| HTMLコンテンツ | curlでテキストが含まれているか | 高 |
| メタデータ | title、description、OGPが動的に生成されているか | 高 |
| 構造化データ | JSON-LDがHTML内に出力されているか | 高 |
| 応答速度 | レスポンスが5秒以内か | 高 |
| サイトマップ | 動的ページがサイトマップに含まれているか | 高 |
まとめ——API駆動ページのGEO対策はサーバーサイド化が鍵
API経由で配信されるコンテンツをAI検索に対応させるには、サーバーサイドでHTMLを生成する仕組みが必要です。 CSRのままでは、AIクローラはコンテンツを読めません。
SSG・ISR・SSRの3つの手法を、コンテンツの更新頻度に応じて使い分けてください。 開発チームに伝えるべきポイントは「AIに読ませたいページをCSRからSSG/SSRに変える」ことです。