製品情報、料金表、事例一覧など、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を生成更新頻度が低いデータ
ISRSSG+定期的なバックグラウンド再生成定期更新データ
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に変える」ことです。