Search Console「検出 - インデックス未登録」が減らない原因:noindexタグページがsitemap.xmlに残っていた話
はじめに
このブログは Next.js 16 を output: 'export' で静的書き出しし、S3 + CloudFront に置く構成です。AdSense 審査に1度落ちたあと、コンテンツ品質改善と並行して Google Search Console(以下 GSC)のインデックス状況も整えてきました。
ところが、修正をいくら重ねても GSC の「ページがインデックスに登録されなかった理由」の件数が想像ほど減りません。「サイトマップが古いのでは?」「noindex の付け方が間違っている?」と仮説を出しては潰す日々で、最終的に sitemap.xml と noindex メタタグが矛盾していた ことが主因と判明しました。
同じような構成(Next.js 静的書き出し+ S3 + CloudFront)でブログを運用していて、GSC の数字が改善しない人の参考になればと思い、調査と修正の流れを記録に残します。
環境
- フレームワーク: Next.js 16(App Router、
output: 'export'、trailingSlash: true) - ホスティング: S3(
tech-challenge-blog)+ CloudFront - ドメイン:
https://cloud-and-code.com - 記事ソース:
posts/*.md(gray-matter + remark) - サイトマップ:
scripts/generate-sitemap.mjsをpostbuildで実行
GSC からエクスポートした「インデックス未登録」レポートはカテゴリごとに以下の件数でした。
| カテゴリ | 件数 | 例 |
|---|---|---|
| アクセス禁止(403) | 8 | draft 化した記事の旧 URL、/bin/sh |
| 見つかりませんでした(404) | 1 | draft 化した記事 |
| noindex タグで除外 | 1 | /tools/aws-free-cost-estimator/ |
| クロール済み - インデックス未登録 | 2〜3 | 1記事しかないタグページ、ツールページ1件 |
| 重複(別の正規ページが選ばれた) | 1 | /posts/ai_and_engineering/ |
| 代替ページ(適切な canonical タグあり) | 15 | 末尾スラッシュなしの URL 群 |
| 検出 - インデックス未登録 | 56 | 多数のタグ・ツール・記事 |
まず疑った仮説と切り分け
仮説1: robots.txt の設定漏れ
draft 記事の URL が 403 として残っているのは、過去に公開していた記事を draft: true に変更したことで起きた現象です。public/robots.txt で該当 URL を Disallow: に追加済みで、かつ実 URL は 404 を返す状態。GSC の表示はクロール時点の記録なので、再クロール待ちです。
仮説2: noindex メタタグが効いていない
/tools/aws-free-cost-estimator/ が「noindex で除外」になっていたので、本番 HTML を直接 curl で確認しました。
curl -s "https://cloud-and-code.com/tools/aws-free-cost-estimator/" \
| grep -oE 'name="robots" content="[^"]*"'
# => name="robots" content="index, follow"
すでに index, follow で配信されていました。GSC の表示が古いだけ。
仮説3: canonical タグの不整合
/posts/ai_and_engineering/ が「重複・別の正規ページ」になっていた件。canonical 値を確認:
curl -s "https://cloud-and-code.com/posts/ai_and_engineering/" \
| grep -oE 'rel="canonical"[^>]*'
# => rel="canonical" href="https://cloud-and-code.com/posts/ai_and_engineering/"
trailingSlash: true を Next.js Metadata API が考慮し、自動でスラッシュを補完していたので問題なし。
ここまでで「GSC 表示は過去のクロール時点のスナップショットで、すでに修正済みのものが多い」とわかりました。残るは「検出 - インデックス未登録」の56件。
真の原因:sitemap × noindex の矛盾
「検出 - インデックス未登録」のリストを目で追っていくと、/tags/cve/、/tags/setup/、/tags/hooks/、/tags/cloudflare/ … と タグページばかり が並んでいます。
このブログのタグページは app/tags/[tag]/page.js で生成しており、記事数が2件未満のタグには noindex を返すようになっていました。
const postCount = posts.filter(
p => Array.isArray(p.tags) && p.tags.includes(tagName)
).length;
const shouldNoIndex = postCount < 2;
return {
// ...
...(shouldNoIndex
? { robots: { index: false, follow: true } }
: {}),
};
実際に curl すると noindex, follow が出ています。ここまでは想定通り。
問題は sitemap でした。scripts/generate-sitemap.mjs を見ると、getAllTags() で取得した 全タグを sitemap に登録 しています。
const tags = getAllTags();
// ...
...tags.map((t) => ({
loc: `/tags/${t.slug}/`,
// ...
})),
つまり Google から見ると次の二重メッセージが届く状態でした。
- sitemap.xml: 「
/tags/cve/を読みに来てね」 - HTML 側: 「読みに来たけど、これは noindex なのでインデックスしないで」
これがまさに「検出 - インデックス未登録」の典型パターンです。Google は sitemap で発見したものの、noindex を見て登録を見送る。リストには残り続ける。減るわけがありません。
公開記事数で集計してみたところ、全57タグのうち 43タグが1記事のみ(noindex 対象)で、14タグが2記事以上(インデックス対象)でした。43件分が無駄に sitemap に乗っていたわけです。
修正コード
scripts/generate-sitemap.mjs で count >= 2 のタグだけを取り込むようにフィルタしました。
// app/tags/[tag]/page.js は記事数 2 件未満のタグを noindex にしているため、
// sitemap でも同じ条件で除外する(noindex を sitemap に含めると Search Console で
// 「検出 - インデックス未登録」として滞留するため)。
const tags = getAllTags().filter((t) => t.count >= 2);
ついでに、postbuild で sitemap を public/sitemap.xml だけに書き出していたのも直しました。Next.js の next build は public/ を out/ に コピーしてから postbuild を実行するため、最新の sitemap が out/ に反映されず、デプロイされる sitemap が常に1ビルド遅れになります。
fs.writeFileSync('public/sitemap.xml', sitemap);
if (fs.existsSync('out')) {
fs.writeFileSync('out/sitemap.xml', sitemap);
}
out/ が存在するときは両方に書き出すよう修正。
ビルド後、URL 数は 109 → 66 に減少。
Sitemap generated: 66 URLs
修正したのに「検証失敗」になった話
ここで一度、GSC の「修正を検証」ボタンを押しました。検証期間は2026/05/07 開始、2026/05/09 に不合格通知。失敗した URL は3件で、内訳は次の通りです。
| URL | 最終クロール | 種別 |
|---|---|---|
/tags/claude-md/ |
2026-05-09 | 1記事しかないタグページ |
/tags/mfa/ |
2026-04-04 | 1記事しかないタグページ |
/tools/aws-architecture-generator/ |
2026-05-06 | インタラクティブ操作主体のツールページ |
落ち着いて原因を切り分けると、ふたつの問題が重なっていました。
原因A: 修正をデプロイしていなかった
前述の sitemap フィルタ修正は、コードを編集しただけで git commit も deploy.sh も実行していませんでした。本番の sitemap.xml は古いままで、Google から見た景色は何ひとつ変わっていない状態。
# 本番に残っていた問題URL
$ curl -s https://cloud-and-code.com/sitemap.xml | grep -E "(claude-md|mfa)"
<loc>https://cloud-and-code.com/tags/claude-md/</loc>
<loc>https://cloud-and-code.com/tags/mfa/</loc>
検証ボタンは「Google に再クロールしてもらう依頼」を出す機能ですが、再クロールしても sitemap が古ければ何も変わりません。この当たり前のことに気付かず、ローカルの sitemap が綺麗になったのを見て満足していました。
教訓: 検証ボタンを押す前に、curl で本番の sitemap.xml と HTML を確認する。 ローカルで直したつもりでも、デプロイ前なら Google には届いていません。
原因B: ツールページは sitemap 除外でも足りない
タグ2件は「sitemap 除外 + ページ側 noindex」で整合性が取れます。一方 /tools/aws-architecture-generator/ は別タイプの問題でした。
- HTML には 2,000文字以上のテキスト(プリセット説明、使い方、構成パターン解説など)が含まれている
- canonical も
noindex指定もなく、他のツールページと同じ実装構造 - にもかかわらず Google は「クロール済み - インデックス未登録」と判定
ツールの中核がドラッグ&ドロップでのノード接続というインタラクティブ操作のため、Google のレンダリングだけでは中身が伝わらず、コンテンツ評価で見送られていると推測しました。他のツール(クイズ、料金計算など)は問題なくインデックスされているので、ツール群全体の問題ではなくこのページ固有の事情です。
対応として、このツールだけ noindex 指定 + sitemap 除外にしました。
// app/tools/aws-architecture-generator/layout.jsx
export const metadata = {
// ...
robots: { index: false, follow: true },
// ...
};
// scripts/generate-sitemap.mjs
const toolSlugs = [
// ... 他のツール
// 'aws-architecture-generator' を除外
];
follow: true のままにしているのは、このページからの内部リンク(他のツールやトップへの導線)はクロールしてほしいためです。
ビルド後の HTML を確認:
$ grep -oE 'name="robots"[^>]*' out/tools/aws-architecture-generator/index.html
name="robots" content="noindex, follow"
$ grep -oE 'name="robots"[^>]*' out/tools/aws-quiz/index.html
name="robots" content="index, follow"
他のツールは index, follow を維持し、対象だけ noindex 化できていることを確認してから再デプロイへ進みます。
「代替ページ(適切な canonical タグあり)」15件はどう扱うか
GSC レポートにあったもう1つのカテゴリ「代替ページ(適切な canonical タグあり)」は、末尾スラッシュなし URL 群です。
https://cloud-and-code.com/posts/aws_secrets_management
https://cloud-and-code.com/about
https://cloud-and-code.com/tags/dev-tools
...
trailingSlash: true で生成されているのは末尾スラッシュ付きの URL ですが、CloudFront は末尾スラッシュなしのリクエストにも同じ HTML を返しています。canonical タグは末尾スラッシュ付きを指しているので、Google は両者を見て「代替ページ・正規版に統合」と正しく判定してくれている状態。
これは「実害がない正常動作」です。CloudFront Functions で 308 リダイレクトを足せばより綺麗にはなりますが、CloudFront の編集権限を別途必要とするので、現状のまま放置で問題ありません。
AdSense 再審査との関係
そもそもこの一連の対応は、4月にAdSense 審査で「有用性の低いコンテンツ」として落ちたことがきっかけでした。コンテンツ品質改善(実体験ベースのリライト・低品質記事の draft 化)と並行して、GSC のインデックス状況も整えています。
AdSense は Google Search のクロール結果を共有しているので、GSC で「検出 - インデックス未登録」が多い状態だと、AdSense 側からは「ほとんど評価対象がないサイト」に見えてしまいます。再審査前に GSC の数字を整えることは、コンテンツ改善と同じくらい重要です。
判断基準としてはこの辺りが目安になりそうです。
- 公開記事のうち 7割以上が GSC でインデックス済み
- 「検出 - インデックス未登録」が 大幅に減少傾向(横ばいや増加なら原因が残っている)
- sitemap と robots.txt と HTML メタタグの 指示が矛盾していない
私は再審査を申請する前に、もう1〜2週間ほど GSC の動きを見て、インデックス済みカウントが2桁後半〜3桁前半に上がってきたタイミングで申請する予定です。
チェックリスト:sitemap と noindex の整合性
同じ罠にハマらないように、Next.js 静的書き出しサイトでよくある不整合パターンをまとめておきます。
- sitemap に登録するページは すべて
index指定 になっているか? -
noindexを返すページが sitemap に 入っていないか? -
robots.txtでDisallow:したページが sitemap に 入っていないか? - canonical の値が 実際に配信される URL と一致 しているか?
-
trailingSlashの設定と sitemap・canonical・内部リンクが 同じ流儀 になっているか? - postbuild の sitemap 生成が
public/だけでなくout/にも反映 されているか? - 削除した記事や draft 化した記事の URL は sitemap から消えている か?
- 修正後、コミット&デプロイ完了 してから検証ボタンを押しているか?(ローカルだけの修正では Google に届かない)
- インタラクティブ操作主体で コンテンツ評価が難しいページ は noindex + sitemap 除外を検討する
特に最初の2つ(sitemap と noindex の整合性)は、自動生成スクリプトを書いていると見落としがちです。タグやカテゴリのように「件数が変動するページ」を扱うときは、生成側で同じ条件でフィルタする癖を付けておくと安全。
まとめ
GSC のインデックス問題で件数が減らないとき、まず疑うべきは「サイト側の指示が矛盾していないか」です。今回のように、sitemap が「読んで」と言いながら HTML が「読まないで」と言っていると、Google は混乱したまま件数だけが残り続けます。
修正後は GSC で 「修正を検証」 ボタンを押すこと、そして主要ページに対して個別に 「インデックス登録をリクエスト」 を実行すること。この2つで Google の再評価サイクルを回せます。
これでもう少し GSC が落ち着いたら、AdSense 再審査に進む予定です。続報があればまた書きます。