記事が80本を超えたあたりで、「この記事は何のキーワード狙いだったか」が書いた本人にも分からなくなりました。そこで狙いキーワード(以下KW)を記事データに機械可読で持たせ、Google Search Console(GSC)の実測と突き合わせ、重複(カニバリ)・未割当・title不一致を検査スクリプトで止める仕組みを作りました。結果として、同じクエリを複数URLが取り合っている箇所が一覧で見えるようになり、デプロイ前に機械で弾けるようになりました。
前提
- サイト: 自社サイト(Astro)
- 規模: ブログ記事80本、固定ページ約90
- 記事データ: 記事ごとのJSON
- 多言語: 一部の記事に翻訳版(en / ko)あり
- リダイレクト:
_redirectsファイルで301を管理。統合済みの旧URLがある - 実測データ: GSC APIで取得した直近92日分の「ページ×クエリ」全量
- 作業体制: 並列エージェント5本(記事40本×2、固定ページ、構造化データ、ツール調査)で半日
なぜ台帳が必要だったか
記事の執筆ルール(執筆用のスキル)には「titleに狙いワードを入れる」と書いてありました。ところが、狙いワードそのものをどこにも保存していませんでした。保存場所は書いた人の頭の中だけです。
この状態だと次のようなことが起きます。
- 記事を書いた時点では狙いがあっても、数ヶ月後には誰も覚えていない
- 新しい記事を書くとき、既存記事と同じKWを狙っていても気づけない
- GSCを見ても「狙った通りに取れているか」を判定する基準が無い
「狙いKWを決めよう」というアドバイスはよく見かけます。ただ、保存場所と一意性の検査が無いと、3ヶ月もすれば消えてしまうというのが今回の実感です。
手順
1. 記事JSONに targetKeyword を追加する
記事ごとのJSONに次の形のフィールドを足しました。
{
"targetKeyword": {
"primary": "<主KW>",
"longtail": ["<ロングテールKW>", "<ロングテールKW>"],
"intent": "<検索意図>",
"cluster": "<クラスタ名>",
"note": "<補足メモ>"
}
}
各フィールドの役割は次のとおりです。
-
primary: その記事の主KW。サイト全体で1つのURLにしか割り当てない -
longtail: 主KWの周辺で拾いたい語。重複してもエラーにしない -
intent: 検索意図 -
cluster: 記事群のまとまり -
note: なぜこのKWにしたか、降格した経緯などのメモ
2. 固定ページ用に seo-map.json を作る
固定ページには記事JSONが無いので、パスをキーにした別ファイルで持たせました。値は記事と同じ形に role を足したものです。
{
"<path>": {
"primary": "<主KW>",
"longtail": ["<ロングテールKW>"],
"intent": "<検索意図>",
"cluster": "<クラスタ名>",
"note": "<補足メモ>",
"role": "target"
}
}
role は次の4値です。
| role | 意味 |
|---|---|
target |
KWを狙って検索流入を取りにいくページ |
brand |
社名・サービス名などの指名検索向け |
support |
直接は狙わないが、他ページを支える |
none |
未分類 |
none は「まだ決めていない」という意味なので、検査ではエラー扱いにしています(後述)。
3. GSC APIで92日分の実測をCSVに落とす
GSC APIで、直近92日分の「ページ×クエリ」の組み合わせを全量取得し、CSVとして保存しました。管理画面で上位だけ眺めるのとは違い、全量を持っておくと「どのURLがどのクエリで表示されているか」を台帳の横に並べられます。
4. 検査スクリプトを書く
scripts/check-seo-map.mjs で次の項目を判定します。
[検査項目]
1. 全記事に targetKeyword がある
2. primary がサイト全体で一意(記事・固定ページをまたいで重複しない)
3. title と h1 に primary の語が入っている
4. 全ルートが台帳(記事JSON or seo-map.json)に登録済み
5. role: none のページが無い
検査の結果は一覧のMarkdown(docs/seo/keyword-map.md)として生成します。URL・主KW・GSCの実測クエリを横に並べた表です。
仕様は docs/seo/KEYWORD-MAP-SPEC.md にまとめ、スクリプトと一覧と同じリポジトリで管理しています。
5. 執筆ルールとpredeployに組み込む
台帳を作っても、新しい記事で入れ忘れれば元に戻ります。そこで次の2か所に組み込みました。
- 執筆スキルの「記事の型」に
targetKeywordを必須項目として追加 - predeploy(デプロイ前の処理)で検査スクリプトを実行し、エラーがあれば止める
執筆スキル → 記事JSONに targetKeyword を必ず書く
↓
predeploy → check-seo-map.mjs → エラーならデプロイしない
起きたこと/落とし穴
実際に80本+約90ページを台帳に載せてGSCの実測と並べたところ、次の問題が出てきました。
落とし穴1: 同じクエリを複数URLが拾っていた
一意性検査をかけると、カニバリが具体的に見えました。
- 「webflow 使い方」: 4つのURLに分裂
- 「インボイス 計算ツール」: 解説記事とツールページで分裂
- 「webflow 制作会社」: 記事とサービスページで衝突
どれも書いているときは別々の記事のつもりでした。並べて初めて取り合いに気づいた例です。
落とし穴2: 301統合済みの旧URLに実測が残る
過去に301で統合した旧URLにも、GSCの92日分には実測が残っています。そのまま集計すると、同じページの数字が旧URLと新URLに割れて、どちらも実態より小さく見えます。
落とし穴3: 翻訳記事の従属ルールが重複判定を起こした
翻訳記事(en / ko)は日本語版に従属させる設計にしていました。つまり翻訳版の台帳は日本語版から自動で派生させる形です。
ところが、日本語版は統合・削除したのに翻訳版だけ残っている記事がありました。この場合、従属先が無い翻訳版が別の記事の主KWを継いでしまい、重複判定になりました。
落とし穴4: 一覧を出すと想定外の拾われ方が見える
エラーではないものの、一覧を見て分かったこともあります。
- 実績ページが情報系のクエリを拾っている
- 韓国語版が日本語の本家より多く表示されているクエリがある
これらは台帳とGSCの実測を同じ表に並べるまで気づきませんでした。
対処
カニバリ: 主KWの重複をエラーにして、負けた側を降格する
「主KWはサイト全体で一意」をエラー条件にしました。重複したら、どちらかを主KWから外して longtail に降格します。
[判定の流れ]
primary が2つ以上のURLで重複
→ エラー(デプロイ停止)
→ 勝つURLを決める
→ 負けたURLは primary を別の語に変え、元の語は longtail へ
今回の具体的な振り分けは次のとおりです。
- 記事側は「選び方」寄りの語を主KWにする
- 「計算ツール」のようなツール語はツールページ側に寄せる
降格した経緯は note に残しておくと、後から「なぜこの記事はこのKWなのか」を追えます。
301の旧URL: _redirects を読んで転送先に合算する
検査スクリプトで _redirects の301を読み、GSCの実測のうち旧URL分を転送先URLに合算するようにしました。
# _redirects の301行(例)
/<旧パス> /<新パス> 301
[集計の流れ]
GSCのCSV(ページ×クエリ)
→ ページが _redirects の301の転送元なら、転送先に読み替え
→ 転送先ごとに合算
これで統合済みページの数字が割れなくなりました。
翻訳記事: seo-map.json の明示登録を優先する
翻訳版は基本的に日本語版から自動派生させたまま、seo-map.json に明示登録があればそちらを優先するルールにしました。
[翻訳版の台帳の決め方]
1. seo-map.json に明示登録がある → それを使う
2. 無い → 日本語版の targetKeyword から自動派生
日本語版が無くなって翻訳版だけ残っている記事は、明示登録で独立した主KWを与えれば重複判定から外れます。
role: none の扱い
未分類の固定ページは none のままだとエラーになります。登録時に必ず target / brand / support のどれかに振り分けることで、「とりあえず台帳に載せただけ」の状態を残さないようにしています。
作業の進め方について
80本+約90ページを一度に台帳に載せるのは手作業だと重いので、並列エージェント5本で分担しました。
- 記事40本 × 2本
- 固定ページ
- 構造化データ
- ツール調査
これで半日で一通り終わりました。分担する前にスキーマ(targetKeyword の形と seo-map.json の形)を仕様書に固めておくと、各エージェントの出力がそのまま検査にかけられます。
まとめ
- 「titleに狙いKWを入れる」ルールがあっても、狙いKW自体を保存していなければ意味が薄い。記事データにフィールドとして持たせる
- 主KWの「サイト全体で一意」を検査のエラーにすると、カニバリが具体的なURLの組として出てくる。負けた側は
longtailに降格する - GSCの実測を集計するときは、301統合済みの旧URLを転送先に合算しないと数字が割れる
- 翻訳版を自動派生させる場合は、明示登録を優先できる逃げ道を用意しておく
- 台帳とGSC実測を横に並べた一覧そのものが、改善点を見つける一番の材料になる