子どもの頃の熱だけを燃料に、ポケモンのファンサイト(Random Pokémon Generator)を趣味で運営しています。無料の非公式ファンサイトですが、せっかく作ったので検索の土台だけは真面目に組みました。その過程でいちばん効いたと思っている判断は、何かを「書いた」ことではなく、サイトマップから情報を「消した」ことでした。
サイトマップの中身
サイトは Next.js 製で、サイトマップは全58 URLです。内訳は固定ページ31本、タイプ別ページ18本、世代別ページ9本。生成コードでは、sitemap の定番フィールドである changeFrequency・priority・lastModified を、意図的に一つも出力していません。
changeFrequency と priority は書くだけ無駄
まず前の2つは簡単で、Google はこれらのフィールドを使わないと公言しています。書いても無視されるだけなので、生成コードから消しました。
lastmod は「不正確なら害」になる
問題は lastModified です。これは Google が実際に参照するフィールドで、だからこそ扱いが難しい。
ありがちな実装は、ビルド時に全 URL へ new Date() を突っ込むものです。楽ですが、これをやると「内容が1文字も変わっていないページ」が、デプロイのたびに全部「更新された」と申告されます。そして不正確な lastmod は、そのページ単体ではなく、サイトマップというファイル全体の信用を毀損します。「このサイトの lastmod は当てにならない」と判断されたら、本当に更新した日にも信じてもらえません。
ページ単位で正確な更新日を追跡する仕組みを持たないなら、書かないのが一番正直です。嘘をつくくらいなら黙る。それだけの話でした。
同じ原則を構造化データにも
この「機械に対して本当のことだけ言う」方針は、構造化データにも通しています。
タイプ別ページの FAQ schema では、回答テキストを joinTypeNames() という関数で生成していますが、画面に表示される文章も同じ関数の出力です。schema 用に別の文字列を組み立てると、いつか表示とマークアップがズレて「見えない内容を機械にだけ主張する」状態になるので、供給源を一本化して一字一句一致を構造で保証しました。
ツールページには WebApplication schema を置き、offers.price="0" を明示しています。実際に全機能無料なので、そのまま申告しているだけです。
おわりに
開発は夜と週末に Claude Code と進めていますが、こういう「何を書かないか」の判断だけは、道具がどれだけ進歩しても人間の仕事として残る気がします。
子どもの頃、図鑑の捕獲記録を盛ってもいいことは何もありませんでした。相手が検索エンジンでも同じです。確かなことだけを申告し、確かでないことは黙る。ファンサイトの SEO の土台は、結局その一行に尽きると思っています。