sitemap drift
本番のsitemapとローカルがずれ、検査を素通りしたページが生まれた記録
sitemapの検査は書いていました。リポジトリとsitemapの照合、noindexとの矛盾、404の検出。それでも本番にだけ存在するページは、どの検査にも映りませんでした。2026年8月に実際に起きた2段階の取りこぼしと、最終的に落ち着いた検査の形を記録します。
このサイトはAnthropicの公式サイトではありません。Claude Codeの機能・料金・提供範囲は変わる可能性があるため、重要な判断の前には公式のドキュメントと自分のアカウント設定を確認してください。
先に結論:sitemapは3つあると考える
ずれを起こしてから整理すると、sitemapと呼んでいたものは実際には3つありました。
| 実体 | 置き場所 | 誰が書くか |
|---|---|---|
| リポジトリのsitemap.xml | 手元のGit | 自分とAI |
| 本番のsitemap.xml | サーバー | デプロイ+本番へ直接書く生成処理 |
| ページの実体 | サーバーのディレクトリ | デプロイ+生成処理 |
検査を「リポジトリ対リポジトリのsitemap」で書くと、3つ目と2つ目のずれが見えません。本番へ直接書き込む経路が1本でもあるなら、検査は本番から取得したものと突き合わせる必要があります。
1回目:189ページがどこにも登録されていなかった(8月7日)
Search Consoleのページ一覧とsitemapを突き合わせたところ、Googleがインデックスしているのにsitemapに無いページが189本ありました。この189本は90日で252クリック・2,978表示を稼いでいました。つまりサイトのトラフィックの大半が、こちらの管理台帳に載っていないページから出ていました。
原因はニュース記事の生成処理が本番へ直接書き込んでいたことです。リポジトリにファイルが無いので、①sitemap未登録 ②内部リンクなし ③その後のサイト横断の修正作業の対象外、という三重の取りこぼしになっていました。
このとき、リポジトリとsitemapを照合する検査(A〜D)を書いて全デプロイ経路に組み込みました。Aはsitemap未登録、Bはリポジトリ欠落、Cはnoindex矛盾、DはFTP走査での本番との差分です。これで解決したと判断しました。
2回目:それでも1ページが素通りした(8月13日)
6日後、本番のsitemapが1,018 URL、ローカルが1,017でした。差の1本を追うと、本番とその sitemap には存在するのに、リポジトリにもローカルのsitemapにも無いページ(本文2,383字・index可)でした。
A〜Dの4検査は全部通っていました。理由は単純で、AとCは「ローカルのsitemap」を基準に、Bは「リポジトリ」を基準にしていて、本番のsitemapを読む検査が1つも無かったからです。Dは本番を見ますがFTP接続が要る手動オプションで、毎回は走りません。
生成処理は本番のsitemapにも追記します。だから本番側だけが1本増え、手元の全ての基準と食い違い、しかもどの自動検査の視野にも入らない。検査は「どこを基準に見るか」で決まり、基準に無い場所のずれは何本書いても映りません。
落ち着いた形:本番のsitemapを毎回取得して差分を取る
検査Eとして、本番のsitemapをHTTPで取得してローカルと差集合を取る処理を足しました。FTP不要なので毎回のデプロイ後に自動で走ります。中心部はこれだけです。
const body = await fetchText('https://サイト/sitemap.xml');
const prod = [...body.matchAll(/<loc>([^<]*)<\/loc>/g)].map(m => toRel(m[1]));
for (const rel of prod) {
if (!localSet.has(rel)) E.push(rel); // 本番にだけある=直接生成の疑い
}
1点だけ注意があります。取得はチャンクを配列に溜めて最後にBuffer.concatで結合してください。ストリームを文字列で連結すると、マルチバイト文字がチャンク境界で壊れます。これで56ページを壊した記録は別ページにあります。
自分のサイトで10秒で確かめる
この欠陥があるかどうかは、公開中のsitemapとローカルのsitemapのURL数を数え比べるだけで最初の当たりが付きます。
curl -s https://あなたのドメイン/sitemap.xml | grep -c "<loc>"
grep -c "<loc>" ローカルのsitemap.xml
2つの数字が一致していれば、少なくとも本数のずれはありません。1本でも違ったら、差分を取って「どちらにだけあるURLか」を特定してください。実測では、この数え比べだけで1,017対1,018の差に気づき、素通りしていた1ページ(本文2,383字)が見つかりました。
数が合っていても中身が違う場合はあるので、確定させるならURL集合の差分まで取ります。それでも所要は数分です。189ページの取りこぼしは、この数分の検査が無かった期間に積み上がりました。
回収したページを検査せずに信用しない
取りこぼしを見つけた後、「本番から取り込んで終わり」にすると次の欠陥を仕込みます。実際に起きた2件です。
- 取り込んだページをsitemapへ機械的に登録したら、薄いページまで登録するところだった。回収した189本のうち2本は本文が実質700〜900字しかなく、審査前のサイトに入れると逆効果でした。登録前に本文量で足切りする分岐を入れました
- 逆に、リポジトリにあるだけで本番に無いページをsitemapへ登録してしまい、404を3本作った。登録の前に本番のHTTPステータスを確認する処理が無かったためです。sitemapに載った404は、検査で見つけた時には既にクロールされています
まとめると、sitemapへの登録は「リポジトリにある」でも「本番にある」でも単独では根拠になりません。両方にあり、200を返し、本文が薄くない。この3つを登録のたびに機械で確かめるのが、8月の2回の失敗の後に残った形です。
よくある質問
生成処理をリポジトリ経由に直せば済む話ではないですか?
長期的にはそうです。ただ、生成経路が複数あると全部を直すまでずれは出続けますし、直した後も「直っているか」を確かめる手段は結局この差分検査になります。経路を直すことと、ずれを検出できることは別の対策として両方要ります。
どのくらいの頻度で走らせていますか?
デプロイのたびに自動で走ります。手動で流すのは調査のときだけです。頻度より「人が思い出さなくても走る」ことのほうが効きました。