あなたのサイトマップは正しく機能していますか?

画像タグ対応XMLサイトマップ――WordPress標準サイトマップを置き換える理由

これまで、インデックス管理のグループを取り上げてきた。ここからはサイトマップのグループに移る。

最初に取り上げるのは、一般的なXMLサイトマップだ。WordPressには、標準で独自のサイトマップ(wp-sitemap.xml)が用意されている。

しかし私は、SOHO SEOを開発するにあたってこれを無効にし、完全に作り直した。今回は、すでに存在するものをなぜあえて置き換えたのか、そしてその過程で実際に直面した問題について話したい。

コアサイトマップの不足点

WordPressコアのサイトマップが抱える最大の問題は、画像情報を十分に含められないことだ。

コアのサイトマップに並ぶのは、URLと最終更新日ほどの情報に限られる。私は、これではDiscoverの時代には不十分だと考えている。このシリーズで繰り返し強調してきたように、Discoverは画像を中心としたチャンネルだからだ。

Googleに画像を適切にクロール・認識させるには、サイトマップの段階から画像情報を明示的に渡す必要がある。そこで私は、<image:image>タグを含むサイトマップを作った。

各記事のサイトマップ項目に代表画像のURLも含め、Googlebotが1回のクロールでテキストと画像を同時に発見できるようにする。

これは、これまでの記事で取り上げてきたクロール段階の効率性の問題に直結する。

クロールバジェットは無限ではない。画像を別の経路で発見させるためにGooglebotを余計に巡回させるより、1つのサイトマップでテキストと画像の情報をまとめて伝えるほうが、はるかに効率的だ。

ほかのプラグインとの場所の奪い合い

サイトマップを独自に作ると決めた瞬間から、注意すべき問題が生じる。

WordPressサイトには、通常、複数のプラグインが同時にインストールされている。Yoast、Rank Math、All in One SEO、SEOPressといったプラグインも、それぞれ独自のサイトマップを生成する。

しかも、その多くが同じアドレス、/sitemap_index.xmlに自分のサイトマップを登録しようとする。これでは場所の奪い合いになる。

私はこの問題を実際に経験した。新しい記事がサイトマップに反映されないという問い合わせがあり、原因を調べてみると、SOHO SEOのサイトマップが更新されていなかったのではなく、以前削除したつもりだった別のプラグインのサイトマップが代わりに応答していたのだ。

そこで現在は、Yoast、Rank Math、AIOSEO、SEOPressが公式に提供している「自分のサイトマップを無効にする」フィルターを、すべて明示的に呼び出している。

対象のプラグインがインストールされていなければ、これらのフィルターは何の影響も与えず、そのまま無視される。このサイトでは、SOHO SEOのサイトマップだけが唯一応答するようにしている。

robots.txtのSitemap行も自分で管理する

robots.txtには慣例として、「Sitemap: [アドレス]」という行を記載し、検索エンジンにサイトマップの場所を知らせる。

ところが私は、この行が偶然に任されているサイトを何度も見てきた。以前インストールしていたYoastが残した行が、そのまま残っているといったケースだ。

この場合、Yoastを完全に削除した瞬間にその行も一緒に消え、検索エンジンが自力でサイトマップを探さなければならなくなる。

そこで私は、ほかのプラグインの有無にかかわらず、この行をSOHO SEOが直接保証するようにした。

同じ行がすでにあれば重複して追加せず、なければ新たに追加する。

末尾のスラッシュ1つでサイトマップがHTMLを返した事故

この機能を作る中で遭遇した、最も厄介なバグについて話したい。

実際に運用しているサイトで、/sitemap_index.xmlや/sitemap-posts.xmlのようなアドレスにリクエストが届くと、WordPressのカノニカルリダイレクト機能がリクエストの末尾にスラッシュを付け、301リダイレクトしてしまう現象が起きていた。

本来なら、リクエストパスにドット(.)が含まれていればファイルとして認識され、このリダイレクトは回避されるはずだ。しかし実際のサーバーでは、そう動作していなかった。

その結果、末尾にスラッシュが付いたアドレスへリダイレクトされ、そのスラッシュ付きのURLはサイトマッププラグインのrewriteルールと一致しなかったため、WordPressがそのままホームページとして処理してしまった。

何が起きたかというと、サイトマップのアドレスにアクセスしたのに、レスポンスは200、Content-Typeはtext/html、実際の内容はブログのホームページだった。

サイトマップがサイトマップではなく、ホームページのHTMLを返していたのだ。

Google Search Consoleはこれを「SitemapがHTMLです」というエラーとして検出した。原因を追跡したものの、ホスティング会社のリダイレクトルールによるものなのか、コアの動作変更によるものなのかを完全には特定できなかった。

しかし、原因を完全に特定できないからといって、手をこまねいているわけにはいかなかった。そこで、SOHO SEOのサイトマップ関連のクエリ変数がすでに認識されているリクエストについては、そもそもカノニカルリダイレクトが実行されないよう明示的に止めることにした。

原因が何であっても再発を防ぐという方法だ。私は、この種のバグこそSEOプラグインの開発で最も恐ろしいものだと思っている。

コード自体に誤りはないのに、WordPressコアの別の動作と組み合わさることで、予想外の結果になるケースだ。

キャッシュが新しい記事を隠してしまう問題

もう1つ注意したのが、キャッシュとの衝突だ。

サイトマップはリクエストごとに再生成されるが、その手前でLiteSpeed Cacheのようなサーバー・CDNキャッシュが以前の時点で作られたコピーを保存していると、no-cacheヘッダーを新たに付けても効果があるのは次のリクエストからにすぎない。

すでに保存されている古いコピーまで削除してくれるわけではない。実際、別のSEOプラグインからSOHO SEOに移行したサイトで、新しい記事が何本も追加された後も、古いサイトマップのコピーがそのまま表示され続けるケースがあった。

そこで、記事が公開・更新・削除されるたびに、サイトマップ関連のアドレスのキャッシュを直ちに能動的に削除するようにした。

どのキャッシュプラグインがインストールされているかを事前に知ることはできない。そのため、LiteSpeed Cache、W3 Total Cache、WP Rocketが提供するURL単位のキャッシュ削除フックを、可能な限りすべて呼び出すようにしている。対象のプラグインがなければ何も実行しない、安全な仕組みだ。

サイト全体のキャッシュを一括で削除するよりも、はるかに軽い処理で目的を達成できる。そして、これも完全ではないことを実サーバー上で改めて確認した。

URL単位の削除は、キャッシュプラグインがそのURLをまったく同じ文字列で保存している場合にしか削除されない。正規化の違いやCDN層の影響で、実際には削除されないこともあった。この問題は今も継続して観察している点だ。

オン・オフをユーザーに委ねた理由

これだけの機能を備えた上で、私は設定画面にサイトマップのオン・オフスイッチを残した。

オフにすると、SOHO SEOはサイトマップに関して本当に何も触らない。WordPressコアのサイトマップも、ほかのSEOプラグインのサイトマップもブロックせず、そのままにする。

これはRank MathやYoastと同じ動作だ。理由は明確で、すでに別のサイトマップソリューションに満足しているユーザーに、SOHO SEOのサイトマップを無理に使わせる必要はないからだ。

このようにしたのはコード構造上の理由による。ジェネレーター自体がすでにWordPressのinitフック実行中に呼び出されるため、ここでrewrite ruleの登録などを後回しにすると、そのリクエストではまったく実行されない問題が起きる。

そのため、設定にかかわらずルールは常に同期的かつ即座に登録し、実際に応答するかどうかだけを設定によって分けている。

SOHO SEOサイトマップ処理の結論

画像タグを含むサイトマップは、一見すると単純な機能に見える。しかし、実際に安定して動作させるには、ほかのプラグインとの場所の奪い合い、WordPressコアによる予期せぬリダイレクト、複数のキャッシュソリューションとの衝突まで、すべてに注意を払わなければならなかった。

この過程で学んだことがある。1つの機能を「作る」ことと、「実際の運用環境で安定して動かす」ことの間には、大きな違いがあるということだ。後者のために費やした時間は、実際には前者を大きく上回った。

私自身、すでに有名なプラグインを使っていて、サイトマップにエラーが出る状況を経験した。そのプラグインだけに問題があったとは言い切れない。しかし、自分でプラグインを作る中で、システムの安全性を確保することは簡単ではない一方、決して不可能でもないと分かった。

経験を通じて、自分にとって最適なシステムを選ぶことも、1つの知恵だといえる。

次回はGoogleニュースサイトマップに移る。なぜ48時間という厳格なルールが存在するのか、そしてそれがDiscoverの鮮度シグナルにどのようにつながるのかを取り上げる予定だ。