Bing Webmaster Toolsが「IndexNow 未設定」と言ってくる。検索流入のほぼ全部がBingなので、Bingが推奨するものは素直に入れることにした。
IndexNowは、検索エンジンに「このURLが新しくなった」とこちらから伝えるための仕組みだ。クロールを待たずに知らせられる。Bing、Yandex、Naverなどが共有していて、1つのエンドポイントに送れば参加エンジン全部に伝わる。
仕組み自体は簡単だ。難しいのは「何を、いつ送るか」のほうだった。
設置: キーを置いてPOSTする
- 任意の英数字のキーを決める(32文字程度)
https://<ホスト>/<キー>.txtに、キーそのものを中身とするファイルを置くhttps://api.indexnow.org/indexnowにJSONをPOSTする
{
"host": "tools.omikuji.dev",
"key": "<キー>",
"keyLocation": "https://tools.omikuji.dev/<キー>.txt",
"urlList": ["https://tools.omikuji.dev/newtool/", "..."]
}
応答は 200 か 202。1リクエストで最大10,000件まで送れる。
キーは「所有者であることの証明」なので、公開されているファイルの中身がキーそのものだ。秘密ではない。ただし他人のホストに対しては使えないので、漏れて困るものでもない。
問題: 同じURLを毎日送ってはいけない
素朴に考えると「毎朝、サイトマップの全URLを送る」になる。これは駄目だ。
IndexNowのガイドラインには、変更のないURLを繰り返し送らないことが明記されている。乱用すると送信元ごと無視される可能性がある。「とりあえず全部送っておけ」は、一番やってはいけない使い方だった。
送るべきなのは、前回から増えた(または変わった)URLだけだ。となると「前回何があったか」を覚えておく必要がある。
前回のサイトマップをどこに置くか
覚えておく場所の候補は3つあった。
- リポジトリにコミットする: 毎朝スナップショットが更新されてコミットが積まれる。履歴が汚れるし、pushがまたデプロイを起こす
- 外部ストレージ: KVでもR2でもいいが、これだけのために口を1つ増やしたくない
- GitHub Actionsのキャッシュ: 無料で、リポジトリを汚さず、消えても困らない
3番にした。消えても困らないのが決め手だ。スナップショットが消えたら、その日はベースラインを取り直すだけで、送信をスキップする。次の日からまた差分が取れる。
- name: Restore previous sitemap snapshot
uses: actions/cache/restore@v4
with:
path: .indexnow-sitemap.txt
key: indexnow-sitemap-${{ github.run_id }}
restore-keys: |
indexnow-sitemap-
- name: Submit new URLs to IndexNow
run: node scripts/indexnow-submit.mjs --diff .indexnow-sitemap.txt
- name: Save sitemap snapshot
if: always()
uses: actions/cache/save@v4
with:
path: .indexnow-sitemap.txt
key: indexnow-sitemap-${{ github.run_id }}
キーに run_id を入れて毎回新しいエントリを作り、restore-keys の前方一致で直近のものを拾う。Actionsのキャッシュはキーの完全一致が無いと前方一致で最新を返すので、これで「前回の」が取れる。
if: always() を付けているのは、送信に失敗した回でもスナップショットは更新したいからだ。更新しないと、次の日に「前回」が2日前になって、昨日の差分が二重に送られる。
スクリプト: 差分を取って送る
if (args[0] === "--diff") {
const snapshotPath = args[1] ?? ".indexnow-sitemap.txt";
const current = await fetchSitemapUrls(); // 本番の sitemap-index を読む
if (!existsSync(snapshotPath)) {
writeFileSync(snapshotPath, current.join("\n") + "\n");
console.log("初回実行: ベースラインを保存しました。送信はしません");
process.exit(0);
}
const prev = new Set(readFileSync(snapshotPath, "utf8").split("\n").filter(Boolean));
const added = current.filter((u) => !prev.has(u));
writeFileSync(snapshotPath, current.join("\n") + "\n");
if (added.length === 0) {
console.log("新規 URL なし。送信はしません");
process.exit(0);
}
await submit(added);
}
やっていることは集合の差だけだ。サイトマップは本番のものを読む。ローカルでビルドしたものではなく、実際に配信されているsitemap-indexを取りに行く。デプロイに失敗して古いままの日に、新しいURLを送ってしまわないためだ。
送信側では、ホストが自分のものでないURLを弾く。サイトマップに他所のURLが混ざることは無いはずだが、誤って他人のホストに送ると、そのホストのキーで検証されて失敗するだけでなく、行儀の悪い送信元として記録される。
const invalid = urlList.filter((u) => !u.startsWith(`https://${HOST}/`));
if (invalid.length > 0) { console.error("..."); process.exit(1); }
途中で変えたこと: 日次リビルドをやめた
このワークフローは、最初 daily-deploy.yml という名前だった。IndexNowを送る前に、サイトの再ビルドを毎朝起こしていた。
毎朝の再ビルドは「念のため」で入っていた。でも調べたら、このサイトは全ページSSRで、ビルド時刻に依存する出力が無い。新しいURLは main へのpushでしか増えない。毎朝ビルドしても、何も変わらないものを作り直しているだけだった。
再ビルドの段を落として、差分送信だけ残した。ファイル名も daily-indexnow.yml に変えた。ビルドは減り、Actionsの消費分も減った。 「念のため」で入れていたものは、たいてい念のためにもなっていない。
効いたのか
正直に書く。順位や表示回数がIndexNowのおかげで上がった、とは言えない。
そもそもIndexNowは順位を上げる仕組みではない。「新しいURLに気づくのを早くする」仕組みだ。効果があるとすれば「公開からクロールまでの時間」だが、IndexNow無しの対照を取っていないので、それも言えない。
同じ時期にtitleとmeta descriptionも直したが、そちらは2か月後にBingのAPIで測ったら順位は動いていなかった。改善2・悪化2・不変1・消失1で、統計的にはノイズだ。IndexNowもtitleも「やって損は無いが、これで数字が動くわけではない」に落ち着いた。
それでも残している理由は、コストがほぼゼロだからだ。毎朝1回、数秒のジョブが走るだけ。ガイドラインに沿って差分だけ送っている限り、害も無い。
まとめ
- IndexNowは「新しいURLを知らせる」仕組み。順位を上げる仕組みではない
- 同じURLを繰り返し送らない。 差分だけ送る。「毎朝全部送る」は乱用にあたる
- 差分を取るには前回のサイトマップが要る。GitHub Actionsのキャッシュに置けば、リポジトリを汚さず、消えてもベースラインを取り直すだけで済む
- キャッシュのキーに
run_idを入れて、restore-keysの前方一致で直近を拾う if: always()でスナップショットは失敗した回も更新する。しないと翌日に二重送信になる- サイトマップは本番のものを読む。 ローカルビルドではなく、実際に配信されているものを
- 自分のホスト以外のURLは送信前に弾く
- 「念のため」の日次リビルドは、SSRサイトでは何も生まなかった。落として問題は起きていない
- 効果は「クロールが早くなる」以上のものではなく、それを測る対照も持っていない。コストがゼロだから残している
「入れると順位が上がる」と思って入れると、がっかりする。「入れておいて損は無い」ものとして入れると、ちょうどいい。