Tech BlogAWSツール & 技術ブログ

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.mjspostbuild で実行

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.mjscount >= 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 buildpublic/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 commitdeploy.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.txtDisallow: したページが 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 再審査に進む予定です。続報があればまた書きます。