blog
canonicalが殻のほうを指していた
ブラウザゲームを外部のポータルサイトへ出す前提で、ビルドを配信先ごとに切り替えられるようにしてある。自分のドメインで配る版と、ポータルへ渡す版が同じコードから出る。
ポータル版には canonical を1本足すようにしている。同じゲームが複数のドメインで配信されるので、検索エンジンに「本体はこっち」と伝えるためだ。宛先は自分のサイトのゲーム説明ページ、/games/<slug> にしてある。設計を書いた時点でそう決めたし、そう書いたつもりだった。
別の作業のついでに、ゲーム3本のビルド設定を横に並べて見ていた。1本だけ違った。
コメントは正しく、式が間違っていた
const canonicalUrl = new URL(`/play/${slug}`, siteUrl).toString();
/play/ になっている。他の2本は /games/ だ。
おかしいのはその2行下で、同じファイルのコメントにはこう書いてある。
ポータル配信ビルド(self 以外)は自ドメインの /games/
を canonical に出し、重複コンテンツを回避する。
コメントは正しい。設計メモも /games/<slug> と書いている。他の2本もそうなっている。その1本の、その1行だけが違っていた。
たぶんコピー元との差分を作るときに触って、そのまま気づかなかったのだと思う。コメントは直さなかったから正しいまま残った。式のほうだけがずれた。
単体では、どちらも正しく見える
これに気づけなかった理由が、あとから見るとはっきりしている。
そのファイルだけを開いて読むと、間違っているとは思わない。/play/<slug> は実在する URL で、ゲーム本体が置いてある場所だ。ポータルから見れば、自分のドメインでそのゲームが遊べる場所を指している。それらしく読める。
3本を縦に並べて初めて、1本だけ違うことが見える。横に並べるまで気づけない種類のずれだった。
しかも設計の意図としては、間違っているほうが素朴に見えるところがある。「ゲームの正体はゲーム本体だから、そこを指す」という理屈は一応立つ。実際にはそうではなくて、検索結果に出したいのは説明があるほうのページなのだが、それは設計の文脈を知らないと出てこない。
別の変更と組み合わせて、矛盾になった
もっと悪いのは、同じ日にもう1つ変更を入れていたことだ。
/play/ 以下は <div id="app">loading…</div> が1つあるだけで、中身は JavaScript が作る。読める文章が1文も無いページだ。説明のあるページと検索結果を取り合うのは損なので、配信ヘッダで X-Robots-Tag: noindex を出すようにした。索引に入れない、という宣言だ。
そこへ、さっきの canonical が重なる。ポータル版が /play/<slug> を canonical に指していると、索引から外すと宣言したページへ、評価を集約させろと言っていることになる。
片方ずつ見ている限り、判断としてはどちらも妥当に見える。索引に入れない、という決定も、canonical を出すという決定も、それぞれ理屈が立つ。canonical の宛先そのものは前述のとおり設計に反していて間違っているのだが、そこに気づいていない状態では、2つの妥当な決定に見えていた。組み合わせて初めて、間違いが矛盾として表に出る。
自分のプロジェクトには「1階層上に同じ穴が無いか見る」という規則を書いてある。sitemap の各 URL に更新日を入れたのに、インデックス側に入れ忘れてクローラが子を取り直さなかった、という失敗から作ったものだ。今回もそれで、片方を直したら、その1つ上と1つ下を見る必要があった。
コメントではなく式を読む検査にした
再発を止めるために、機械の検査を1つ足した。3本のビルド設定を読んで、canonical の宛先が /games/ で始まっているかを見る。
書きながら少し考えたのは、何を読ませるかだった。コメントを読ませる案もあり得たが、今回の件はまさにコメントが正しくて式が間違っていたケースだ。コメントは嘘をつける。というより、コメントは変更しなくても壊れないから、実装だけがずれても平気で正しいまま残る。だから式のほうを読ませることにした。
足した検査は、正しい状態で緑になっても意味が無い。壊れた状態で赤になることを確かめないと、何も見ていない検査と区別が付かない。なので実際に /play/ へ戻してみて、落ちること、どのゲームがどこを指しているかまで報告することを確認してから戻した。
それとは別に、実際にポータル向けのビルドを走らせて、出てきた HTML を見た。
<link rel="canonical" href="https://ckludge.com/games/kludge-works" />
ソースを読む検査は「そう書いてある」ことしか保証しない。これはつい先日、別の記事で自分が書いたばかりの話だった。書いた直後に同じ形の検査を足しているのだから、少なくとも1回は実物を通しておくべきだと思った。
実物を1つしか通していなかった
公開前にもう一度確かめて、ここが足りていなかったと分かった。配信先は3つあるのに、走らせたのは1つだけだった。
残り2つのビルド成果物を開いたら、こうなっていた。
| 成果物 | 生成日 | canonical |
|---|---|---|
| poki | 08-31 | /games/kludge-works |
| crazygames | 07-28 | /play/kludge-works |
| pochi | 07-28 | /play/kludge-works |
ソースは直っている。検査も緑。それでも手元の成果物2つは、直す前の canonical を持ったままだった。 修正の後に作り直していないので当然なのだが、当然なことに気づいていなかった。
出荷物として git に入っているわけではないので、実害は出ていない。ただ、このまま zip に固めてポータルへ出せば、直したはずの間違いをそのまま送ることになる。ソースを読む検査は、この状態を1つも捕まえない。「そう書いてある」ことしか保証しない、というのはこういう意味だった。
3つとも作り直して、3つとも /games/ を出すことを確認した。
直したこと自体は小さい
変更は1行だ。/play/ を /games/ にしただけで、それ以外は何も触っていない。
ただ、この1行が間違っていることに気づくまで1か月かかっている。誰も見ていないし、テストも通るし、ポータルへはまだ出していないので実害も出ていない。出してから気づいたら、向こうのページの評価を、自分が索引から外したページへ流し続けるところだった。
3本並べて見た、というだけで見つかった。同じ形のものが複数あるなら、一度は横に並べてみるのがいいのだと思う。1本ずつ読んでいる限り、たぶん永久に気づかなかった。
このビルド設定で配信しているのがKludge Worksで、いまは自分のドメインだけで動いている。