0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

記事80本のAstroサイトで「1URL=1主KW」を台帳と検査で守る手順

0
Posted at

記事が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実測を横に並べた一覧そのものが、改善点を見つける一番の材料になる
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?