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.tsとsitemap.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 | クエリに依存、キャッシュ困難 |
| ダッシュボード | CSR | AI検索の対象外 |
迷ったら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点です。