Next.jsのSEO対策、「基本的なmetaタグ設定」だけで満足していませんか?
Next.jsは、その強力なサーバーサイドレンダリング(SSR)や静的サイト生成(SSG)機能により、「SEOに強い」フレームワークとして広く認識されています。確かに、React製のSPA(シングルページアプリケーション)が抱えていた「JavaScriptが無効だとコンテンツが読み込めない」といった初期のSEO課題を、Next.jsは見事に解決しました。
しかし、Next.jsを採用するだけで、あるいは next/head や App Router の Metadata オブジェクトで title タグや description を設定するだけで、SEO対策が完了したと考えるのは早計です。
クローラビリティとリッチリザルトの課題
基本的なmetaタグ設定は、いわば「最低限の身だしなみ」に過ぎません。実際の運用では、以下のような、より高度な課題に直面します。
- クローラビリティの問題: サイトのページ数が増えるにつれ、Googleのクローラー(Googlebot)がサイトの全ページを効率的に発見し、クロールすることが難しくなります。特に、ブログ記事や商品ページなど、動的に生成されるページが多数存在する場合、それらがGoogleに正しく認識されているか不安が残ります。
- リッチリザルトの不在: 検索結果画面で、競合他社のサイトにはパンくずリスト、評価(星マーク)、FAQなどが表示されているのに、自社サイトはシンプルなテキストリンクだけ、という状況はありませんか? これは、検索エンジンに対してコンテンツの意味を伝える「構造化データ」が不足している証拠です。
これらの課題を放置することは、Next.jsのポテンシャルを最大限に活かしきれていない状態と言えます。
「とりあえず」のSEO対策が招く、大きな機会損失
なぜmetaタグだけでは不十分なのか
Googleのクローラーは非常に高度ですが、万能ではありません。サイトの「案内図」である sitemap.xml が提供されなければ、クローラーはサイト内のリンクを辿ってページを発見するしかありません。新しいページが追加されたり、深い階層にあるページが存在したりすると、インデックス登録までに時間がかかるか、最悪の場合、見落とされる可能性もあります。
sitemap.xmlは、サイト運営者が「クロールしてほしいページのリスト」を検索エンジンに明示的に伝えるための重要なファイルです。これを最新の状態で提供し続けることは、SEOの基本中の基本です。
競合は「構造化データ」で差をつけている
検索順位(Ranking)が同じでも、検索結果でのクリック率(CTR)には大きな差が生まれます。その要因の一つが「リッチリザルト」です。
リッチリザルトは、構造化データ(Schema.org)を用いて、ページのコンテンツが「何であるか」(例えば、「この記事はブログ記事である」「これは商品のレビューである」)を検索エンジンに伝えることで表示されます。
構造化データが実装されていないサイトは、検索結果で目立つ機会を失い、結果としてCTRが低下し、ビジネスチャンスを逃している可能性があるのです。
「next-sitemap」と「JSON-LD」でNext.jsのSEOを飛躍させる
前述の課題を解決し、Next.jsサイトのSEOを一段階引き上げるために、私たちは以下の2つの実装を強く推奨します。
解決策の全体像:2つの柱
- クローラビリティの最適化:
next-sitemapによるsitemap.xmlの自動生成
サイトのビルドプロセスと連携し、常に最新のsitemap.xmlを自動生成する仕組みを構築します。これにより、手動での更新漏れを防ぎ、クローラーの巡回を最適化します。 - セマンティックな情報伝達:
Schema.org準拠のJSON-LDによる構造化データの実装
検索エンジンがコンテンツの意味を正確に理解できるよう、JSON-LD形式で構造化データを埋め込みます。これにより、リッチリザルトの表示を促し、CTRの向上を目指します。
これら2つを適切に実装することで、Next.jsサイトは技術的SEOの観点から「万全」の状態に近づきます。
実践ガイド①:`next-sitemap` の導入と設定
next-sitemap は、Next.jsプロジェクトのsitemap.xmlとrobots.txtを簡単に生成・管理できる非常に便利なライブラリです。
インストールと基本設定
まずは、プロジェクトにライブラリをインストールします。
# npm の場合
npm install next-sitemap --save-dev
# yarn の場合
yarn add next-sitemap --dev次に、package.json の scripts に、ビルド後にsitemapを生成するコマンドを追加します。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"postbuild": "next-sitemap", // この行を追加
"start": "next start"
}
}これで、npm run build(または yarn build)を実行するたびに、ビルドプロセス(next build)が完了した後、自動的に next-sitemap が実行されるようになります。
設定ファイル(next-sitemap.config.js)の解説
プロジェクトのルートディレクトリに next-sitemap.config.js を作成し、基本的な設定を行います。
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: 'https://www.your-domain.com', // 必須:サイトのドメイン
generateRobotsTxt: true, // robots.txt も自動生成する場合 (true)
outDir: './public', // 出力先ディレクトリ (Next.jsのデフォルトに合わせて './public')
// ... その他のオプション
};siteUrl は必須項目です。ここに正しいドメインを指定してください。generateRobotsTxt: true に設定すると、sitemap.xml へのパスを記述した robots.txt も同時に生成・上書きしてくれるため便利です。(既存の robots.txt がある場合は内容を確認してください)
動的ルート(ダイナミックルーティング)への対応
/pages/blog/[slug].js や /app/products/[id]/page.tsx のような動的ルートは、next-sitemap が自動で検出できません。これらのページをsitemapに含めるには、追加の設定が必要です。
(注:next-sitemap V4以降では、動的ルートのsitemap生成方法が変更され、多くの場合、追加設定なしでも .next/ ディレクトリのビルド情報から検出可能になりました。しかし、明示的な制御が必要な場合や古いバージョンでは、以下の設定が役立ちます。)
例えば、外部APIから取得した記事リストをsitemapに含めたい場合、additionalPaths オプションやカスタムスクリプトを利用する方法がありますが、最もモダンな方法は、動的ページ側で getStaticProps や getServerSideProps(またはApp Routerの generateStaticParams)を使っている場合、next-sitemap がビルド時のマニフェストを読み取って自動生成してくれることです。
もし特定のページ(例:ログイン必須ページ)を除外したい場合は、exclude オプションを使用します。
module.exports = {
siteUrl: 'https://www.your-domain.com',
generateRobotsTxt: true,
exclude: ['/mypage', '/admin/*'], // '/mypage' と '/admin/' 配下を除外
};実践ガイド②:Next.jsにJSON-LD(Schema.org)を実装する
次に、検索エンジンにコンテンツの意味を伝える「構造化データ」をJSON-LD形式で実装します。
JSON-LDとは? なぜ重要か?
JSON-LD (JavaScript Object Notation for Linked Data) は、構造化データをWebページに埋め込むための形式の一つです。Schema.org が定義するボキャブラリ(語彙)を使い、「この記事は Article タイプで、著者は Person タイプのこの人」といった情報を、検索エンジンが解釈できる形で記述します。
これを <script type="application/ld+json"> タグとしてHTMLの <head> 内(または <body> 内)に配置します。
実装パターン1:サイト全体(Organization)とパンくずリスト(BreadcrumbList)
サイト全体に共通する情報(運営組織の情報)や、各ページに表示されるパンくずリストは、共通コンポーネント(例: Layout コンポーネント)で実装するのが効率的です。
以下は、App Router のルートレイアウト (/app/layout.tsx) や Pages Router (/pages/_app.tsx) で共通のJSON-LDを埋め込む例です。
// 例: 共通のJSON-LDを生成するコンポーネント (JsonLdCommon.tsx)
import React from 'react';
// 組織情報
const organizationSchema = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'あなたの会社名',
url: 'https://www.your-domain.com',
logo: 'https://www.your-domain.com/logo.png', // ロゴ画像のURL
contactPoint: {
'@type': 'ContactPoint',
telephone: '+81-3-XXXX-XXXX', // 電話番号
contactType: 'customer service', // 問い合わせ窓口の種類
},
};
// パンくずリスト(例:トップページ > ブログ)
const breadcrumbSchema = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: 'ホーム',
item: 'https://www.your-domain.com/',
},
{
'@type': 'ListItem',
position: 2,
name: 'ブログ',
item: 'https://www.your-domain.com/blog',
},
],
};
export const JsonLdCommon = () => {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }}
/>
{/* パンくずリストは動的に変わるため、ページごとに生成するのが望ましいが、共通部分として実装する例 */}
{/* <script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
/> */}
</>
);
};
(注:パンくずリストは通常、現在のページ階層に応じて動的に生成する必要があります。上記はあくまで構造の例です)
実装パターン2:記事(Article)や商品(Product)ごとの動的生成
ブログ記事や商品詳細ページなど、コンテンツごとに情報が異なる場合は、そのページコンポーネント内で動的にJSON-LDを生成します。
以下は、ブログ記事ページ(例: /app/blog/[slug]/page.tsx)で Article タイプのJSON-LDを生成する例です。
// 例: /app/blog/[slug]/page.tsx (App Router)
// 記事データを取得する非同期関数 (例)
async function getPostData(slug: string) {
// ... fetch処理 ...
return {
title: 'Next.js SEOガイド',
publishedTime: '2025-11-09T12:00:00+09:00',
modifiedTime: '2025-11-09T12:00:00+09:00',
authorName: '高橋 健太',
// ...
};
}
// 記事ページのコンポーネント
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await getPostData(params.slug);
// 記事用のJSON-LDデータを動的に生成
const articleSchema = {
'@context': 'https://schema.org',
'@type': 'Article',
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://www.your-domain.com/blog/${params.slug}`,
},
headline: post.title,
// image: [ ... 画像URL ... ],
datePublished: post.publishedTime,
dateModified: post.modifiedTime,
author: {
'@type': 'Person',
name: post.authorName,
},
publisher: { // 運営組織情報(共通コンポーネントと重複しないよう注意)
'@type': 'Organization',
name: 'あなたの会社名',
logo: {
'@type': 'ImageObject',
url: 'https://www.your-domain.com/logo.png',
},
},
};
return (
<article>
{/* JSON-LDのscriptタグを埋め込む */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleSchema) }}
key="article-jsonld" // Reactのkey
/>
<h1>{post.title}</h1>
{/* ... 記事本文 ... */}
</article>
);
}
実装のポイントと検証
JSON-LDを実装する際は、以下の点に注意してください。
必須プロパティと推奨プロパティ
Schema.org(およびGoogleのドキュメント)には、タイプごとに必須のプロパティと推奨されるプロパティが定義されています。リッチリザルトの対象となるよう、必要な情報を漏れなく記述することが重要です。
検証ツールの活用
実装後は、必ずGoogleの「リッチリザルト テスト」や「Schema Markup Validator」を使用して、構文エラーや必須項目の漏れがないかを確認してください。
Next.jsのSEOを「強み」に変える第一歩
next-sitemap によるクローラビリティの確保と、JSON-LD によるセマンティックな情報伝達。これら2つは、Next.jsサイトのSEOを「とりあえず」のレベルから「万全」のレベルへと引き上げるための、強力な武器となります。
今すぐ取り組むべきこと
まずは、ご自身のサイトの現状を把握することから始めましょう。
https://www.your-domain.com/sitemap.xmlにアクセスし、sitemapが正しく生成・更新されているか確認する。- Google Search Consoleに登録し、「インデックス作成 > サイトマップ」でsitemapが認識されているか、「拡張機能」セクションで構造化データのエラー(または認識状況)を確認する。
もし、sitemapが存在しない、または構造化データが未実装であるならば、本記事で紹介した手法の導入をぜひご検討ください。
【CTA】専門家によるSEO診断・実装支援
「記事の内容は理解できたが、自社サイトの複雑な構造にどう適用すればいいか分からない」
「リソースが足りず、JSON-LDの正確な実装まで手が回らない」
このような課題をお持ちの場合、ぜひ一度、弊社にご相談ください。
Next.jsに精通したテクニカルSEOスペシャリストとエンジニアが、サイト診断から next-sitemap の導入、Schema.orgに準拠した最適なJSON-LDの実装まで、ワンストップでサポートいたします。

