Next.jsは、SSR・SSG・ISRという3つのレンダリング方式を選択できるフレームワークです。 GEO対策の観点では、どの方式を選ぶかで「AIクローラがコンテンツを読めるかどうか」が変わります。

この記事では、各レンダリング方式のGEO特性を整理し、App Routerでのメタデータ・構造化データの実装方法をコード付きで解説します。

3つのレンダリング方式とGEOへの影響

方式動作タイミングAIクローラへの影響適したコンテンツ
SSGビルド時にHTML生成確実に読めるブログ記事、ドキュメント
ISR初回ビルド+バックグラウンド再生成確実に読める頻繁に更新されるコンテンツ
SSRリクエスト時にHTML生成読めるが応答速度に依存ユーザー固有のページ
CSRブラウザでJS実行後に描画読めないダッシュボード、管理画面

GEO対策で最も安全なのはSSGです。 ビルド時にHTMLが確定するため、AIクローラがアクセスした瞬間にすべてのテキストが読めます。

ISRは「ほぼSSG」として機能します。 再生成中も古いキャッシュが返されるため(stale-while-revalidate)、AIクローラが読めなくなることはありません。

SSGで記事ページを生成する

ブログ記事のように内容が固定されるページは、generateStaticParamsでビルド時に生成します。

// app/blog/[slug]/page.tsx
import { getArticleBySlug, getAllSlugs } from '@/lib/articles';
import { notFound } from 'next/navigation';

export async function generateStaticParams() {
  const slugs = await getAllSlugs();
  return slugs.map((slug) => ({ slug }));
}

export default async function ArticlePage({
  params,
}: {
  params: { slug: string };
}) {
  const article = await getArticleBySlug(params.slug);
  if (!article) notFound();

  return (
    <article>
      <h1>{article.title}</h1>
      <time dateTime={article.publishedAt}>
        {new Date(article.publishedAt).toLocaleDateString('ja-JP')}
      </time>
      <div dangerouslySetInnerHTML={{ __html: article.contentHtml }} />
    </article>
  );
}

ISRに切り替える場合は、export const revalidate = 3600;を追加するだけです。 1時間ごとにバックグラウンドでHTMLが再生成されます。

Metadata APIでメタデータを設定する

App RouterのgenerateMetadataで、ページごとに動的なメタデータを生成します。

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';

export async function generateMetadata({
  params,
}: {
  params: { slug: string };
}): Promise<Metadata> {
  const article = await getArticleBySlug(params.slug);
  if (!article) return { title: '記事が見つかりません' };

  return {
    title: article.title,
    description: article.description,
    openGraph: {
      title: article.title,
      description: article.description,
      type: 'article',
      publishedTime: article.publishedAt,
      modifiedTime: article.updatedAt,
    },
    alternates: {
      canonical: `https://example.com/blog/${params.slug}`,
    },
  };
}

generateMetadataの返り値は、Next.jsが<head>内のメタタグとして自動出力します。 AIクローラはこのメタタグを参照して、記事のタイトル・説明・公開日を取得します。

構造化データ(JSON-LD)の実装

App Routerでは、<script type="application/ld+json">をコンポーネント内で直接出力します。

// components/ArticleJsonLd.tsx
interface Props {
  title: string;
  description: string;
  publishedAt: string;
  updatedAt?: string;
  authorName: string;
  url: string;
}

export function ArticleJsonLd(props: Props) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: props.title,
    description: props.description,
    datePublished: props.publishedAt,
    ...(props.updatedAt && { dateModified: props.updatedAt }),
    author: { '@type': 'Person', name: props.authorName },
    publisher: { '@type': 'Organization', name: '自社名' },
    mainEntityOfPage: { '@type': 'WebPage', '@id': props.url },
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
    />
  );
}

JSON-LDはHTMLの一部としてビルド時に確定するため、AIクローラが確実に読み取れます。

robots.tsとサイトマップの実装

App Routerでは、robots.tssitemap.tsをファイルベースで生成できます。

// app/robots.ts
import type { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      { userAgent: '*', allow: '/' },
      { userAgent: 'GPTBot', allow: '/' },
      { userAgent: 'PerplexityBot', allow: '/' },
      { userAgent: 'ClaudeBot', allow: '/' },
    ],
    sitemap: 'https://example.com/sitemap.xml',
  };
}
// app/sitemap.ts
import type { MetadataRoute } from 'next';
import { getAllArticles } from '@/lib/articles';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const articles = await getAllArticles();

  return [
    { url: 'https://example.com', changeFrequency: 'weekly', priority: 1 },
    ...articles.map((a) => ({
      url: `https://example.com/blog/${a.slug}`,
      lastModified: new Date(a.updatedAt || a.publishedAt),
      priority: 0.7,
    })),
  ];
}

Next.jsはこれらのファイルから/robots.txt/sitemap.xmlを自動生成します。

SSRページの注意点

SSRを使うページでは、AIクローラへの応答速度が重要です。 サーバーの応答時間を5秒以内に抑えてください(AIクローラのタイムアウトが短いため)。

// CDNキャッシュでSSRの応答速度を改善
export async function GET() {
  const data = await fetchData();
  return Response.json(data, {
    headers: {
      'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600',
    },
  });
}

stale-while-revalidateを設定すると、キャッシュ期限後も古いレスポンスを返しつつバックグラウンドで更新します。

レンダリング方式の選択基準

コンテンツの性質推奨方式理由
ブログ記事SSG内容が固定、ビルド時に確定可能
事例紹介SSG公開後の変更頻度が低い
料金表ISR(1時間)月次で更新、リアルタイム性は不要
検索結果ページSSRクエリに依存、キャッシュ困難
ダッシュボードCSRAI検索の対象外

迷ったらSSGを選んでください。 SSGが最もAIクローラに対して安全で、サーバーコストも低いです。

ビルドの検証

デプロイ前に、ビルド出力を確認します。

npm run build && npm run start

# AIクローラとしてアクセス
curl -A "GPTBot" http://localhost:3000/blog/sample-article | head -50

ビルドログの出力記号でがSSG、がSSR/ISRです。 記事ページがになっていることを確認してください。

まとめ——Next.jsのGEO対策は方式選択から始まる

Next.jsでのGEO対策は、レンダリング方式の選択が出発点です。 ブログ記事にはSSG、更新頻度が高いコンテンツにはISR、ユーザー固有のページにはSSRを使います。

開発チームに伝えるべきポイントは「記事ページをSSGにする」「Metadata APIを使う」「JSON-LDを埋め込む」の3点です。