[{"data":1,"prerenderedAt":954},["ShallowReactive",2],{"content-/search-console-404-redirect-fix":3,"all-pages-for-dir":933,"related-/search-console-404-redirect-fix":934,"og-image-/search-console-404-redirect-fix":953},{"id":4,"title":5,"body":6,"category":914,"concepts":915,"description":916,"extension":917,"meta":918,"navigation":235,"ogImage":915,"path":919,"project_name":920,"published":921,"publishedAt":922,"seo":923,"stem":924,"tags":925,"todo":931,"unpublished":921,"updatedAt":915,"__hash__":932},"pages/2026-04/2026-04-14/search-console-404-redirect-fix.md","Search Console 404を329件発見し、リダイレクトルールで75%を潰した",{"type":7,"value":8,"toc":901},"minimark",[9,13,17,20,25,28,138,157,161,166,177,281,285,296,387,391,403,509,512,516,522,525,547,730,733,737,740,763,766,769,772,775,874,877,897],[10,11,5],"h1",{"id":12},"search-console-404を329件発見しリダイレクトルールで75を潰した",[14,15,16],"p",{},"3月31日にリダイレクトルールを整備してから、ちょうど2週間。「2週間後に効果を見る」とカレンダーに入れていた日が今日だった。Chrome DevTools MCPでSearch Consoleを直接開き、ページインデックス作成の画面を表示した。",[14,18,19],{},"404の数字が目に飛び込んできた。329件。手が止まった。",[21,22,24],"h2",{"id":23},"csvを落として全件分析する","CSVを落として全件分析する",[14,26,27],{},"Search ConsoleからCSVをエクスポートし、329件のURLをパターン別に分類した。",[29,30,31,50],"table",{},[32,33,34],"thead",{},[35,36,37,41,44,47],"tr",{},[38,39,40],"th",{},"パターン",[38,42,43],{},"件数",[38,45,46],{},"例",[38,48,49],{},"対応",[51,52,53,71,85,104,121],"tbody",{},[35,54,55,59,62,68],{},[56,57,58],"td",{},"slugベース（日付なし）",[56,60,61],{},"多数",[56,63,64],{},[65,66,67],"code",{},"/some-article",[56,69,70],{},"リダイレクト対象",[35,72,73,76,78,83],{},[56,74,75],{},"トレイリングスラッシュ付き",[56,77,61],{},[56,79,80],{},[65,81,82],{},"/some-article/",[56,84,70],{},[35,86,87,93,96,101],{},[56,88,89,92],{},[65,90,91],{},"/docs/"," 配下",[56,94,95],{},"少数",[56,97,98],{},[65,99,100],{},"/docs/old-page",[56,102,103],{},"放置（旧構造の残骸）",[35,105,106,111,113,118],{},[56,107,108,92],{},[65,109,110],{},"/_nuxt/",[56,112,95],{},[56,114,115],{},[65,116,117],{},"/_nuxt/builds/meta/xxx",[56,119,120],{},"放置（ビルドアーティファクト）",[35,122,123,128,130,135],{},[56,124,125,92],{},[65,126,127],{},"/path/",[56,129,95],{},[56,131,132],{},[65,133,134],{},"/path/old-route",[56,136,137],{},"放置（存在しないルート）",[14,139,140,144,145,148,149,152,153,156],{},[141,142,143],"strong",{},"slugベースURL","と",[141,146,147],{},"トレイリングスラッシュ付きURL","の2つが大半を占めていた。つまり、Googlebotは ",[65,150,151],{},"/article-slug"," と ",[65,154,155],{},"/article-slug/"," の両方でクロールしており、どちらも404を返していた。",[21,158,160],{"id":159},"generate-redirectsmjsを3回書き直す","generate-redirects.mjsを3回書き直す",[162,163,165],"h3",{"id":164},"問題1-newpathがslugベースのままだった","問題1: newPathがslugベースのままだった",[14,167,168,169,172,173,176],{},"リダイレクトの向き先が ",[65,170,171],{},"/{slug}"," になっていたが、実際のコンテンツは frontmatter の ",[65,174,175],{},"path"," フィールドで定義している。slugとpathが一致しないケースで、リダイレクト先が404になるという本末転倒な状態だった。",[178,179,184],"pre",{"className":180,"code":181,"language":182,"meta":183,"style":183},"language-javascript shiki shiki-themes vitesse-light vitesse-light","// Before: slugをそのまま使っていた\nconst newPath = '/' + slug;\n\n// After: frontmatterのpathを正規URLとして使用\nconst canonicalPath = extractPath(content) || '/' + slug;\n","javascript","",[65,185,186,195,230,237,243],{"__ignoreMap":183},[187,188,191],"span",{"class":189,"line":190},"line",1,[187,192,194],{"class":193},"sxvE3","// Before: slugをそのまま使っていた\n",[187,196,198,202,206,210,214,218,221,224,227],{"class":189,"line":197},2,[187,199,201],{"class":200},"stQ0i","const",[187,203,205],{"class":204},"s4oTP"," newPath",[187,207,209],{"class":208},"shFtX"," =",[187,211,213],{"class":212},"sMJiu"," '",[187,215,217],{"class":216},"sdGka","/",[187,219,220],{"class":212},"'",[187,222,223],{"class":200}," +",[187,225,226],{"class":204}," slug",[187,228,229],{"class":208},";\n",[187,231,233],{"class":189,"line":232},3,[187,234,236],{"emptyLinePlaceholder":235},true,"\n",[187,238,240],{"class":189,"line":239},4,[187,241,242],{"class":193},"// After: frontmatterのpathを正規URLとして使用\n",[187,244,246,248,251,253,257,260,263,266,269,271,273,275,277,279],{"class":189,"line":245},5,[187,247,201],{"class":200},[187,249,250],{"class":204}," canonicalPath",[187,252,209],{"class":208},[187,254,256],{"class":255},"senZ8"," extractPath",[187,258,259],{"class":208},"(",[187,261,262],{"class":204},"content",[187,264,265],{"class":208},")",[187,267,268],{"class":200}," ||",[187,270,213],{"class":212},[187,272,217],{"class":216},[187,274,220],{"class":212},[187,276,223],{"class":200},[187,278,226],{"class":204},[187,280,229],{"class":208},[162,282,284],{"id":283},"問題2-トレイリングスラッシュのルールが抜けていた","問題2: トレイリングスラッシュのルールが抜けていた",[14,286,287,288,291,292,295],{},"Googlebotは ",[65,289,290],{},"/slug/"," でクロールするが、Nuxtは ",[65,293,294],{},"/slug"," で配信する。トレスラ付きURLのリダイレクトルールを追加した。",[178,297,299],{"className":180,"code":298,"language":182,"meta":183,"style":183},"// トレスラ付きも追加\nif (!from.endsWith('/')) {\n  redirects.add(`${from}/ ${to} 301`);\n}\n",[65,300,301,306,341,382],{"__ignoreMap":183},[187,302,303],{"class":189,"line":190},[187,304,305],{"class":193},"// トレスラ付きも追加\n",[187,307,308,312,315,318,321,324,327,329,331,333,335,338],{"class":189,"line":197},[187,309,311],{"class":310},"sHkkW","if",[187,313,314],{"class":208}," (",[187,316,317],{"class":200},"!",[187,319,320],{"class":204},"from",[187,322,323],{"class":208},".",[187,325,326],{"class":255},"endsWith",[187,328,259],{"class":208},[187,330,220],{"class":212},[187,332,217],{"class":216},[187,334,220],{"class":212},[187,336,337],{"class":208},"))",[187,339,340],{"class":208}," {\n",[187,342,343,346,348,351,353,356,359,361,364,367,369,372,374,377,379],{"class":189,"line":232},[187,344,345],{"class":204},"  redirects",[187,347,323],{"class":208},[187,349,350],{"class":255},"add",[187,352,259],{"class":208},[187,354,355],{"class":212},"`",[187,357,358],{"class":310},"${",[187,360,320],{"class":216},[187,362,363],{"class":310},"}",[187,365,366],{"class":216},"/ ",[187,368,358],{"class":310},[187,370,371],{"class":216},"to",[187,373,363],{"class":310},[187,375,376],{"class":216}," 301",[187,378,355],{"class":212},[187,380,381],{"class":208},");\n",[187,383,384],{"class":189,"line":239},[187,385,386],{"class":208},"}\n",[162,388,390],{"id":389},"問題3-slug-path-のときスキップされていた","問題3: slug === path のときスキップされていた",[14,392,393,396,397,399,400,402],{},[65,394,395],{},"from === to"," のガード条件で、slug直叩きのトレスラ対応がスキップされていた。slugとpathが同一の記事では ",[65,398,290],{}," → ",[65,401,294],{}," のルールが生成されない。ガード条件の中でトレスラルールだけは生成するように修正した。",[178,404,406],{"className":180,"code":405,"language":182,"meta":183,"style":183},"if (from === to) {\n  // 同一パスでもトレスラ付き→なしのルールは必要\n  if (!from.endsWith('/')) {\n    redirects.add(`${from}/ ${to} 301`);\n  }\n  return;\n}\n",[65,407,408,426,431,458,491,496,504],{"__ignoreMap":183},[187,409,410,412,414,416,419,422,424],{"class":189,"line":190},[187,411,311],{"class":310},[187,413,314],{"class":208},[187,415,320],{"class":204},[187,417,418],{"class":200}," ===",[187,420,421],{"class":204}," to",[187,423,265],{"class":208},[187,425,340],{"class":208},[187,427,428],{"class":189,"line":197},[187,429,430],{"class":193},"  // 同一パスでもトレスラ付き→なしのルールは必要\n",[187,432,433,436,438,440,442,444,446,448,450,452,454,456],{"class":189,"line":232},[187,434,435],{"class":310},"  if",[187,437,314],{"class":208},[187,439,317],{"class":200},[187,441,320],{"class":204},[187,443,323],{"class":208},[187,445,326],{"class":255},[187,447,259],{"class":208},[187,449,220],{"class":212},[187,451,217],{"class":216},[187,453,220],{"class":212},[187,455,337],{"class":208},[187,457,340],{"class":208},[187,459,460,463,465,467,469,471,473,475,477,479,481,483,485,487,489],{"class":189,"line":239},[187,461,462],{"class":204},"    redirects",[187,464,323],{"class":208},[187,466,350],{"class":255},[187,468,259],{"class":208},[187,470,355],{"class":212},[187,472,358],{"class":310},[187,474,320],{"class":216},[187,476,363],{"class":310},[187,478,366],{"class":216},[187,480,358],{"class":310},[187,482,371],{"class":216},[187,484,363],{"class":310},[187,486,376],{"class":216},[187,488,355],{"class":212},[187,490,381],{"class":208},[187,492,493],{"class":189,"line":245},[187,494,495],{"class":208},"  }\n",[187,497,499,502],{"class":189,"line":498},6,[187,500,501],{"class":310},"  return",[187,503,229],{"class":208},[187,505,507],{"class":189,"line":506},7,[187,508,386],{"class":208},[14,510,511],{},"この3つの修正を順に入れるたびにルール数が膨らんでいった。1,653件 → 3,554件 → 最終的に4,309件。",[21,513,515],{"id":514},"cloudflare-pagesの2000ルール制限に衝突する","Cloudflare Pagesの2,000ルール制限に衝突する",[14,517,518,521],{},[65,519,520],{},"_redirects","ファイルを生成してデプロイしたところ、ルールが2,000行を超えた分は無視される仕様に引っかかった。4,309件のうち、2,001行目以降はただのテキストとして無視される。",[14,523,524],{},"対策として、ルールに優先順位をつけた。",[526,527,528,535],"ol",{},[529,530,531,534],"li",{},[141,532,533],{},"優先ルール（先頭に配置）:"," 404 CSVに実際に載っていたURLとトレスラ付きルール",[529,536,537,540,541,399,544,546],{},[141,538,539],{},"低優先ルール（後半に配置）:"," 旧日付パス形式（",[65,542,543],{},"/2025-01-01/slug",[65,545,294],{},"）",[178,548,550],{"className":180,"code":549,"language":182,"meta":183,"style":183},"// トレスラ・slug直叩きルールを優先\nconst priority = allRedirects.filter(r => !r.match(/^\\/\\d{4}-\\d{2}[\\/-]/));\nconst rest = allRedirects.filter(r => r.match(/^\\/\\d{4}-\\d{2}[\\/-]/));\nconst sortedRedirects = [...priority.sort(), ...rest.sort()];\n",[65,551,552,557,636,694],{"__ignoreMap":183},[187,553,554],{"class":189,"line":190},[187,555,556],{"class":193},"// トレスラ・slug直叩きルールを優先\n",[187,558,559,561,564,566,569,571,574,576,579,582,585,587,589,592,594,596,599,603,607,611,615,617,620,623,625,628,631,633],{"class":189,"line":197},[187,560,201],{"class":200},[187,562,563],{"class":204}," priority",[187,565,209],{"class":208},[187,567,568],{"class":204}," allRedirects",[187,570,323],{"class":208},[187,572,573],{"class":255},"filter",[187,575,259],{"class":208},[187,577,578],{"class":204},"r",[187,580,581],{"class":208}," =>",[187,583,584],{"class":200}," !",[187,586,578],{"class":204},[187,588,323],{"class":208},[187,590,591],{"class":255},"match",[187,593,259],{"class":208},[187,595,217],{"class":212},[187,597,598],{"class":310},"^",[187,600,602],{"class":601},"svWSF","\\/",[187,604,606],{"class":605},"she3A","\\d",[187,608,610],{"class":609},"sM54T","{4}",[187,612,614],{"class":613},"sLkVR","-",[187,616,606],{"class":605},[187,618,619],{"class":609},"{2}",[187,621,622],{"class":208},"[",[187,624,602],{"class":601},[187,626,614],{"class":627},"snbK4",[187,629,630],{"class":208},"]",[187,632,217],{"class":212},[187,634,635],{"class":208},"));\n",[187,637,638,640,643,645,647,649,651,653,655,657,660,662,664,666,668,670,672,674,676,678,680,682,684,686,688,690,692],{"class":189,"line":232},[187,639,201],{"class":200},[187,641,642],{"class":204}," rest",[187,644,209],{"class":208},[187,646,568],{"class":204},[187,648,323],{"class":208},[187,650,573],{"class":255},[187,652,259],{"class":208},[187,654,578],{"class":204},[187,656,581],{"class":208},[187,658,659],{"class":204}," r",[187,661,323],{"class":208},[187,663,591],{"class":255},[187,665,259],{"class":208},[187,667,217],{"class":212},[187,669,598],{"class":310},[187,671,602],{"class":601},[187,673,606],{"class":605},[187,675,610],{"class":609},[187,677,614],{"class":613},[187,679,606],{"class":605},[187,681,619],{"class":609},[187,683,622],{"class":208},[187,685,602],{"class":601},[187,687,614],{"class":627},[187,689,630],{"class":208},[187,691,217],{"class":212},[187,693,635],{"class":208},[187,695,696,698,701,703,706,709,711,714,717,720,723,725,727],{"class":189,"line":239},[187,697,201],{"class":200},[187,699,700],{"class":204}," sortedRedirects",[187,702,209],{"class":208},[187,704,705],{"class":208}," [...",[187,707,708],{"class":204},"priority",[187,710,323],{"class":208},[187,712,713],{"class":255},"sort",[187,715,716],{"class":208},"(),",[187,718,719],{"class":208}," ...",[187,721,722],{"class":204},"rest",[187,724,323],{"class":208},[187,726,713],{"class":255},[187,728,729],{"class":208},"()];\n",[14,731,732],{},"日付パス形式のURLは古いインデックスが剥がれれば404も減るので、後回しにしても実害は小さい。",[21,734,736],{"id":735},"結果-329件中248件をカバー","結果: 329件中248件をカバー",[14,738,739],{},"デプロイ後、curlで何件かサンプリングして301が返ることを確認した。",[741,742,743,749],"ul",{},[529,744,745,748],{},[141,746,747],{},"カバー済み:"," 248件（75%） -- slugベース + トレスラ付き",[529,750,751,754,755,757,758,757,760,762],{},[141,752,753],{},"放置:"," 81件（25%） -- ",[65,756,91],{},"、",[65,759,110],{},[65,761,127],{}," 等のシステムパス",[14,764,765],{},"残り81件は実在しないシステムパスで、リダイレクトしようがない。Googlebotが自然にクロール対象から外すのを待てばいい。",[21,767,768],{"id":768},"次のアクション",[14,770,771],{},"2週間後（4月28日）にもう一度Search Consoleを確認するリマインダーをGoogleカレンダーに登録した。今回カバーした248件の404が減っていれば成功。",[21,773,774],{"id":774},"試行錯誤まとめ",[29,776,777,793],{},[32,778,779],{},[35,780,781,784,787,790],{},[38,782,783],{},"#",[38,785,786],{},"試したこと",[38,788,789],{},"結果",[38,791,792],{},"気づき",[51,794,795,813,830,846,860],{},[35,796,797,800,807,810],{},[56,798,799],{},"1",[56,801,802,803,806],{},"slugベースで",[65,804,805],{},"newPath","生成",[56,808,809],{},"pathと不一致の記事でリダイレクト先が404",[56,811,812],{},"frontmatterのpathを正として使う",[35,814,815,818,821,827],{},[56,816,817],{},"2",[56,819,820],{},"トレスラなしルールだけ生成",[56,822,823,824,826],{},"Googlebotの ",[65,825,290],{}," アクセスが404のまま",[56,828,829],{},"Googlebotはトレスラ付きでもクロールする",[35,831,832,835,840,843],{},[56,833,834],{},"3",[56,836,837,839],{},[65,838,395],{}," で全スキップ",[56,841,842],{},"slug=pathの記事のトレスラ対応が漏れた",[56,844,845],{},"ガード条件の中でも例外処理が要る",[35,847,848,851,854,857],{},[56,849,850],{},"4",[56,852,853],{},"4,309ルールをそのままデプロイ",[56,855,856],{},"2,001行目以降が無視された",[56,858,859],{},"Cloudflare Pagesは2,000ルール上限",[35,861,862,865,868,871],{},[56,863,864],{},"5",[56,866,867],{},"優先ルールを先頭配置",[56,869,870],{},"404 CSVの75%をカバー",[56,872,873],{},"「実際に404になっているURL」を優先するのが合理的",[21,875,876],{"id":876},"学び",[741,878,879,886,892],{},[529,880,881,882,885],{},"Search Consoleの404は画面をスクロールしても全体像が見えない。",[141,883,884],{},"CSVに落として分類表を作る","と、5分でパターンが浮かび上がる",[529,887,888,889,891],{},"Cloudflare Pagesの",[65,890,520],{},"は2,000ルールで打ち止めになる。超えた分は黙って捨てられるので、「どのルールを先頭に置くか」を考えないとデプロイ後に404が減らない",[529,893,894,896],{},[65,895,395],{}," のガード条件は「全部スキップ」ではなく「一部だけ通す」が正しい場合がある。トレスラのように元ルールの派生で生まれるルールを見落としやすい",[898,899,900],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .svWSF, html code.shiki .svWSF{--shiki-default:#BDA437;--shiki-dark:#BDA437}html pre.shiki code .she3A, html code.shiki .she3A{--shiki-default:#5A6AA6;--shiki-dark:#5A6AA6}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .sLkVR, html code.shiki .sLkVR{--shiki-default:#AB5E3F;--shiki-dark:#AB5E3F}html pre.shiki code .snbK4, html code.shiki .snbK4{--shiki-default:#A65E2B;--shiki-dark:#A65E2B}",{"title":183,"searchDepth":197,"depth":197,"links":902},[903,904,909,910,911,912,913],{"id":23,"depth":197,"text":24},{"id":159,"depth":197,"text":160,"children":905},[906,907,908],{"id":164,"depth":232,"text":165},{"id":283,"depth":232,"text":284},{"id":389,"depth":232,"text":390},{"id":514,"depth":197,"text":515},{"id":735,"depth":197,"text":736},{"id":768,"depth":197,"text":768},{"id":774,"depth":197,"text":774},{"id":876,"depth":197,"text":876},"dev",null,"3月末のリダイレクト修正から2週間後にSearch Consoleを確認。404が329件残っていたのでCSV全件分析し、generate-redirects.mjsを改修。Cloudflare Pagesの2,000ルール制限と格闘しながら248件をカバーした記録。","md",{},"/search-console-404-redirect-fix","mdx-playground",false,"2026-04-14T00:00:00.000Z",{"title":5,"description":916},"2026-04/2026-04-14/search-console-404-redirect-fix",[926,927,928,929,930],"SEO","Search Console","リダイレクト","Cloudflare Pages","404","memo","njBk0K2AVucF7fc4g97tafW5s8W82C6ZZC0j4amkBKo",[],[935,939,943,946,950],{"title":936,"path":937,"publishedAt":938},"Search Console効果測定で未登録5,030件の主因を特定し、デプロイエラーまで片付けた日","/search-console-audit-and-deploy","2026-07-04T00:00:00.000Z",{"title":940,"path":941,"publishedAt":942},"Chrome DevTools MCP autoConnect設定とSearch Console 404対策の一日","/chrome-devtools-autoconnect-search-console","2026-03-31T00:00:00.000Z",{"title":944,"path":945,"publishedAt":922},"2026年4月14日の開発日記 - CF精算表デバッグ10セッション一気通貫","/2026-04-14-diary",{"title":947,"path":948,"publishedAt":949},"2026年3月4日の開発日記 - 鍵屋サイトを一日で作り替えてPageSpeed94点まで磨いた","/2026-03-04-diary","2026-03-04T00:00:00.000Z",{"title":951,"path":952,"publishedAt":949},"PageSpeedモバイル94点達成までの最適化ログとSEO改善の記録","/pagespeed-seo-optimization-2026-03-04","https://log.eurekapu.com/og/blog/search-console-404-redirect-fix.png?v=2026-04-14T00%3A00%3A00.000Z&title=Search%20Console%20404%E3%82%92329%E4%BB%B6%E7%99%BA%E8%A6%8B%E3%81%97%E3%80%81%E3%83%AA%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%A775%25%E3%82%92%E6%BD%B0%E3%81%97%E3%81%9F&author=Kei%20Komatsu&sig=0b5e5f358399bbf9",1784510952903]