孤立ページを構造的に作らない:sitemapに書く前に一覧から辿れるかを検証する
静的サイトにページを自動追加するスクリプトを書くと、ほぼ確実にこの事故が起きる。
新しいページのHTMLは生成された。sitemap.xml にも追記された。でも一覧ページにリンクが入っていない。
スクリプトは終了コード0を返す。ファイルは増えている。デプロイも通る。数週間後にSEO監査をして初めて、「トップから <a href> をたどっても到達できないページ」が何十枚も溜まっていることに気づく。
私が運用しているClaude Codeガイドという静的サイトで、実際にこれをやりかけた。原因は間抜けなもので、一覧が public/index.html ではなく public/tutorials/index.html にあったのに、スクリプトが前者を書き換えていた。書き換え自体は「成功」するので、誰も気づかない。
なぜ「成功したのに壊れている」が起きるのか
原因は、スクリプトの成功条件が「書き込めたこと」になっている点にある。
ページ追加スクリプトがやることを分解すると、こうなる。
- 記事HTMLを書く
- 一覧ページにカードを挿し込む
- 一覧の構造化データ(ItemList)の position を採番し直す
- 見出しの「(N件)」を更新する
- sitemap.xml にURLを追記する
このうち1と5は単独で完結するので、まず失敗しない。一方2〜4は既存HTMLの中の特定の場所を探して書き換えるので、セレクタや見出しIDが変わった瞬間に静かに空振りする。
そして厄介なことに、1と5だけが成功した状態は、外から見ると最悪の形になる。
- sitemap には載っているのでクローラは来る
- しかしサイト内のどこからもリンクされていない
- リンクが無いページは、クローラから見て重要度が低い
つまり「作った」のに「無いのと同じ」ページが増えていく。人がSEO監査を回すまで、誰も検知しない。
直し方:出力ではなく「到達できること」を検証する
対策はシンプルで、スクリプトの最後に自分が書いたものを読み直して、リンクが実在するかを見る。書いたつもりではなく、書いた結果を検証する。
実際に入れているのは、この十数行だ。
1 | |
ポイントは2つある。
1つ目は、検証の対象が「操作の戻り値」ではなく「最終的な文字列」であること。 挿入関数が成功を返したかではなく、挿入後のHTMLに実際にそのhrefがあるかを見る。挿入関数のバグも、セクションIDの取り違えも、これなら全部ここで止まる。
2つ目は、この検証が sitemap 追記より前にあること。 順番が逆だと、落ちたときに sitemap にだけURLが残る。まさに防ぎたかった状態そのものになってしまう。
同じ考え方で、セクションIDの妥当性も先に見ている。
1 | |
存在しないセクションを指定したら、どこかに適当に入れるのではなく落とす。 「とりあえず末尾に入れておく」といった親切心が、いちばん検知しづらい壊れ方を生む。
ついでに、ItemList の position は挿入のたびに以降が全部ずれるので、増分更新をやめて毎回カードの並びから作り直している。
1 | |
見出しの「(N件)」も同じく実カード数から書き直す。増分ではなく再計算にすると、過去にずれた分もそのとき一緒に直るという副産物がある。
実際に試す:既存サイトの孤立ページを数える
ここまでは「これから追加するページ」の話だが、既に溜まっている分も知りたい。トップから <a href> だけを辿って到達できるURLの集合と、sitemap.xml に載っているURLの集合を比べればいい。
依存パッケージなしで動く。Node.js 18以降(グローバルの fetch を使う)で、対象は自分が運用しているサイトに向けること。
orphan-check.mjs として保存する。
1 | |
実行した結果(2026-08-01・Node.js v20.17.0 で実測)がこれだ。
1 | |
読むときに注意する点が2つある。
クロール上限で打ち切った分を「孤立」と数えてはいけない。 上の実装では最後に警告を出しているが、LIMIT に達した実行の結果は未確定として扱う。確定させたいなら上限を外す必要があり、そのぶん相手のサーバーに負荷がかかる。日次で回すならサンプリング、週次で全件、という使い分けが現実的だ。
JavaScriptを実行していないのは意図的だ。 クローラが最初に受け取るHTMLに <a href> が無いなら、それはリンクとして数えないほうが実態に近い。一覧をクライアント側のJavaScriptだけで描画しているサイトは、このスクリプトで「全部孤立」と出る。それは検出漏れではなく、そういう状態だということだ。
また、フォーム送信でしか到達できない /search のようなページは、当然この方法では到達できない。実質的に孤立しているという判定は正しいので、sitemapから外すか、どこかからリンクするかを決めることになる。
まとめ
自動生成スクリプトのバグは、「落ちるバグ」より「静かに成功するバグ」のほうが高くつく。落ちれば気づくが、成功したように見えるものは、人が監査するまで積み上がり続ける。
やることは2つだけだ。
- 書き込みの成否ではなく、書き込んだ結果を読み直して検証する
- 検証に落ちたら、部分的な成果(sitemapへの追記)を残さずに落とす
sitemap への追記を検証の後ろに置く、という順番だけでも効果がある。順番はコストゼロで変えられる。
この仕組みで運用しているサイトの実物はClaude Codeガイドから見られる。一覧・構造化データ・sitemap が同じスクリプトで一括更新されているので、ページを増やしても導線がずれない。