Search Console 404を329件発見し、リダイレクトルールで75%を潰した

開発mdx-playgroundメモ

Search Console 404を329件発見し、リダイレクトルールで75%を潰した

3月31日にリダイレクトルールを整備してから、ちょうど2週間。「2週間後に効果を見る」とカレンダーに入れていた日が今日だった。Chrome DevTools MCPでSearch Consoleを直接開き、ページインデックス作成の画面を表示した。

404の数字が目に飛び込んできた。329件。手が止まった。

CSVを落として全件分析する

Search ConsoleからCSVをエクスポートし、329件のURLをパターン別に分類した。

パターン件数対応
slugベース(日付なし)多数/some-articleリダイレクト対象
トレイリングスラッシュ付き多数/some-article/リダイレクト対象
/docs/ 配下少数/docs/old-page放置(旧構造の残骸)
/_nuxt/ 配下少数/_nuxt/builds/meta/xxx放置(ビルドアーティファクト)
/path/ 配下少数/path/old-route放置(存在しないルート)

slugベースURLトレイリングスラッシュ付きURLの2つが大半を占めていた。つまり、Googlebotは /article-slug/article-slug/ の両方でクロールしており、どちらも404を返していた。

generate-redirects.mjsを3回書き直す

問題1: newPathがslugベースのままだった

リダイレクトの向き先が /{slug} になっていたが、実際のコンテンツは frontmatter の path フィールドで定義している。slugとpathが一致しないケースで、リダイレクト先が404になるという本末転倒な状態だった。

// Before: slugをそのまま使っていた
const newPath = '/' + slug;

// After: frontmatterのpathを正規URLとして使用
const canonicalPath = extractPath(content) || '/' + slug;

問題2: トレイリングスラッシュのルールが抜けていた

Googlebotは /slug/ でクロールするが、Nuxtは /slug で配信する。トレスラ付きURLのリダイレクトルールを追加した。

// トレスラ付きも追加
if (!from.endsWith('/')) {
  redirects.add(`${from}/ ${to} 301`);
}

問題3: slug === path のときスキップされていた

from === to のガード条件で、slug直叩きのトレスラ対応がスキップされていた。slugとpathが同一の記事では /slug//slug のルールが生成されない。ガード条件の中でトレスラルールだけは生成するように修正した。

if (from === to) {
  // 同一パスでもトレスラ付き→なしのルールは必要
  if (!from.endsWith('/')) {
    redirects.add(`${from}/ ${to} 301`);
  }
  return;
}

この3つの修正を順に入れるたびにルール数が膨らんでいった。1,653件 → 3,554件 → 最終的に4,309件。

Cloudflare Pagesの2,000ルール制限に衝突する

_redirectsファイルを生成してデプロイしたところ、ルールが2,000行を超えた分は無視される仕様に引っかかった。4,309件のうち、2,001行目以降はただのテキストとして無視される。

対策として、ルールに優先順位をつけた。

  1. 優先ルール(先頭に配置): 404 CSVに実際に載っていたURLとトレスラ付きルール
  2. 低優先ルール(後半に配置): 旧日付パス形式(/2025-01-01/slug/slug
// トレスラ・slug直叩きルールを優先
const priority = allRedirects.filter(r => !r.match(/^\/\d{4}-\d{2}[\/-]/));
const rest = allRedirects.filter(r => r.match(/^\/\d{4}-\d{2}[\/-]/));
const sortedRedirects = [...priority.sort(), ...rest.sort()];

日付パス形式のURLは古いインデックスが剥がれれば404も減るので、後回しにしても実害は小さい。

結果: 329件中248件をカバー

デプロイ後、curlで何件かサンプリングして301が返ることを確認した。

  • カバー済み: 248件(75%) -- slugベース + トレスラ付き
  • 放置: 81件(25%) -- /docs//_nuxt//path/ 等のシステムパス

残り81件は実在しないシステムパスで、リダイレクトしようがない。Googlebotが自然にクロール対象から外すのを待てばいい。

次のアクション

2週間後(4月28日)にもう一度Search Consoleを確認するリマインダーをGoogleカレンダーに登録した。今回カバーした248件の404が減っていれば成功。

試行錯誤まとめ

#試したこと結果気づき
1slugベースでnewPath生成pathと不一致の記事でリダイレクト先が404frontmatterのpathを正として使う
2トレスラなしルールだけ生成Googlebotの /slug/ アクセスが404のままGooglebotはトレスラ付きでもクロールする
3from === to で全スキップslug=pathの記事のトレスラ対応が漏れたガード条件の中でも例外処理が要る
44,309ルールをそのままデプロイ2,001行目以降が無視されたCloudflare Pagesは2,000ルール上限
5優先ルールを先頭配置404 CSVの75%をカバー「実際に404になっているURL」を優先するのが合理的

学び

  • Search Consoleの404は画面をスクロールしても全体像が見えない。CSVに落として分類表を作ると、5分でパターンが浮かび上がる
  • Cloudflare Pagesの_redirectsは2,000ルールで打ち止めになる。超えた分は黙って捨てられるので、「どのルールを先頭に置くか」を考えないとデプロイ後に404が減らない
  • from === to のガード条件は「全部スキップ」ではなく「一部だけ通す」が正しい場合がある。トレスラのように元ルールの派生で生まれるルールを見落としやすい