[{"data":1,"prerenderedAt":372},["ShallowReactive",2],{"content-/unpublished-noindex":3,"all-pages-for-dir":370,"og-image-/unpublished-noindex":371},{"id":4,"title":5,"body":6,"category":351,"description":352,"extension":353,"meta":354,"navigation":355,"ogImage":356,"path":357,"project_name":358,"published":359,"publishedAt":360,"seo":361,"stem":362,"tags":363,"todo":356,"unpublished":359,"updatedAt":356,"__hash__":369},"pages/2026-06/2026-06-01/unpublished-noindex.md","非公開記事一覧をGoogleにインデックスさせない仕組みを点検した話",{"type":7,"value":8,"toc":344},"minimark",[9,13,18,30,33,44,158,164,168,182,188,216,222,225,228,231,234,237,248,256,263,274,278,281,302,321,328,331,334,340],[10,11,12],"p",{},"非公開記事を検索結果から消すために noindex の meta タグを書こうとして、手を止めた。調べたら、本番ビルドが非公開記事を最初から焼かない仕組みが既にあって、meta robots より強い「そもそも HTML が存在しない」状態が出来上がっていた。今日は「noindex を入れる作業」だと思って着手したのに、終わってみたら「既存実装が要件を満たすことの確認作業」だった。",[14,15,17],"h2",{"id":16},"きっかけunpublished-一覧検索に出てない","きっかけ：unpublished 一覧、検索に出てない?",[10,19,20,21,25,26,29],{},"ローカルの ",[22,23,24],"code",{},"localhost:3000/blog/unpublished"," に億り人まとめ記事を出していた。これは ",[22,27,28],{},"unpublished: true"," を立てた記事だけを集める非公開の一覧ページだ。",[10,31,32],{},"ここで気になった。このページに並んでいる記事、本番に出たら Google にインデックスされてしまうのか。検索結果に出したくない。URL を直打ちされるアクセスは最悪しょうがないが、できればそれも防ぎたい。",[10,34,35,36,39,40,43],{},"最初に頭に浮かんだのは ",[22,37,38],{},"\u003Cmeta name=\"robots\" content=\"noindex\">"," だった。実際、",[22,41,42],{},"app/composables/useNoindex.ts"," にパスパターンで noindex を当てる仕組みが既にある。フォームページや薄いチャートページをここで弾いている。",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-ts shiki shiki-themes vitesse-light vitesse-light","const NOINDEX_PATTERNS: RegExp[] = [\n  /^\\/blog\\/tax-consultation-form\\/?$/,\n  /^\\/blog\\/company_chart_absolute\\/?$/,\n  // ...\n]\n","ts","",[22,52,53,80,119,145,152],{"__ignoreMap":50},[54,55,58,62,66,70,74,77],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"stQ0i","const ",[54,63,65],{"class":64},"s4oTP","NOINDEX_PATTERNS",[54,67,69],{"class":68},"shFtX",": ",[54,71,73],{"class":72},"sSkh3","RegExp",[54,75,76],{"class":68},"[] =",[54,78,79],{"class":68}," [\n",[54,81,83,87,91,95,99,101,104,106,110,113,116],{"class":56,"line":82},2,[54,84,86],{"class":85},"sMJiu","  /",[54,88,90],{"class":89},"sHkkW","^",[54,92,94],{"class":93},"svWSF","\\/",[54,96,98],{"class":97},"sLkVR","blog",[54,100,94],{"class":93},[54,102,103],{"class":97},"tax-consultation-form",[54,105,94],{"class":93},[54,107,109],{"class":108},"sM54T","?",[54,111,112],{"class":89},"$",[54,114,115],{"class":85},"/",[54,117,118],{"class":68},",\n",[54,120,122,124,126,128,130,132,135,137,139,141,143],{"class":56,"line":121},3,[54,123,86],{"class":85},[54,125,90],{"class":89},[54,127,94],{"class":93},[54,129,98],{"class":97},[54,131,94],{"class":93},[54,133,134],{"class":97},"company_chart_absolute",[54,136,94],{"class":93},[54,138,109],{"class":108},[54,140,112],{"class":89},[54,142,115],{"class":85},[54,144,118],{"class":68},[54,146,148],{"class":56,"line":147},4,[54,149,151],{"class":150},"sxvE3","  // ...\n",[54,153,155],{"class":56,"line":154},5,[54,156,157],{"class":68},"]\n",[10,159,160,163],{},[22,161,162],{},"/blog/unpublished"," をこの配列に足せば終わり——そう思ってルーティング構造と既存の漏洩防止ロジックを読み始めた。",[14,165,167],{"id":166},"調べて手が止まったnoindex-より強い壁が既にあった","調べて手が止まった：noindex より強い壁が既にあった",[10,169,170,171,174,175,174,178,181],{},"読み進めて、考えが変わった。",[22,172,173],{},"mark-unpublished.mjs","、",[22,176,177],{},"seo-noindex.ts",[22,179,180],{},"verify-unpublished-excluded.mjs"," という一式が既に存在していた。過去の自分が、同じ要望をもっと徹底した形で実装済みだった。",[10,183,184,185,187],{},"仕組みはこうだ。",[22,186,28],{}," を立てた記事は、本番ビルドから6経路すべてで除外される。",[189,190,191,195,201,204,207,210],"ul",{},[192,193,194],"li",{},"HTML 本体",[192,196,197,200],{},[22,198,199],{},"_raw"," の md ファイル",[192,202,203],{},"payload（Nuxt のデータ）",[192,205,206],{},"sitemap",[192,208,209],{},"redirects",[192,211,212,215],{},[22,213,214],{},"_nuxt"," バンドル",[10,217,218,219,221],{},"そして ",[22,220,162],{}," ページ自体が本番では 404 になる。",[10,223,224],{},"noindex の meta タグは「クロールはされるが検索結果には出すな」というお願いでしかない。今回の実装はそれより一段強くて、本番にそもそも HTML が生成されない。検索エンジンが取りに来ても 404 が返る。",[10,226,227],{},"ここで「URL 直打ちは最悪しょうがない」と諦めていた点も解決していることに気づいた。HTML が焼かれていないのだから、本番で URL を直打ちされても 404 になる。閲覧できるのは dev（localhost）だけだ。meta robots を当てる出番がなかった。",[14,229,230],{"id":230},"検証していたら別のビルド事故を踏んだ",[10,232,233],{},"「本当に除外されているか」を確かめるため、漏洩検出テストとビルド検証を回した。ここで2つ、別の問題が表に出てきた。",[10,235,236],{},"ひとつは、公開記事から非公開記事へのリンクが2本残っていたこと（5/22 作業の積み残し）。漏洩を解説する記事自身が、例示として Markdown のリンク記法を本文に書いてしまい、それ自体が漏洩源になっていた。自己参照漏洩だ。リンク記法をプレーンテキストに直して、検出テスト9件を緑にした。",[10,238,239,240,243,244,247],{},"もうひとつが厄介だった。",[22,241,242],{},"pnpm generate"," がプリレンダー完了後（3541ルート生成済み）に exit 1 で落ちた。ビルド本体は成功していて、その後の検証スクリプト ",[22,245,246],{},"verify-blog-payload.mjs"," が落としていた。",[45,249,254],{"className":250,"code":252,"language":253},[251],"language-text","[verify-blog-payload] dist/blog/index.html: 1 article hrefs (1 unique)\n  - Only 1 unique article links in /blog page HTML (expected at least 20).\n","text",[22,255,252],{"__ignoreMap":50},[10,257,258,259,262],{},"「記事リンクが1本しかない、20本未満だ」という判定だった。だがフィルタは無傷で（",[22,260,261],{},"article-date.ts"," のテスト24件は全部通る）、これは検証スクリプトの誤検知だった。",[10,264,265,266,269,270,273],{},"原因は日付依存だった。",[22,267,268],{},"/blog"," のデフォルト表示はカレンダーで、SSG のプリレンダー HTML にはカレンダーだけが焼き込まれる。",[22,271,272],{},"BlogCalendar.vue"," は当月を初期表示する。今日は6月1日。6月にはまだ記事が2本しかない。なのにスクリプトは「日付プレフィックス付きリンクが20本以上」を厳密カウントで要求していた。月をまたいだ瞬間に当月記事数が20を割って引っかかる。5/31までは5月カレンダーが満載だったから通っていただけだった。",[14,275,277],{"id":276},"verify-blog-payloadmjs-を件数からカバレッジに書き換えた","verify-blog-payload.mjs を「件数」から「カバレッジ」に書き換えた",[10,279,280],{},"絶対件数の閾値という発想が脆かった。スクリプトを「カレンダー表示月のカバレッジ検証」に直した。",[189,282,283,289,292,299],{},[192,284,285,288],{},[22,286,287],{},"BLOG_MIN_ARTICLES"," という脆いゲートを撤廃",[192,290,291],{},"アプリと同じ規則で「プリレンダーされるカレンダーの表示月」を算出",[192,293,294,295,298],{},"frontmatter から ",[22,296,297],{},"updatedAt"," も読み、カレンダーのグルーピング日付に一致させる",[192,300,301],{},"検証は3点：(1) content リンクが0本なら全面リグレッション、(2) 当月の最新記事が HTML に無ければ月末比較バグ、(3) 当月公開の記事が全部リンクされているか",[10,303,304,305,308,309,312,313,316,317,320],{},"カレンダーは1日の記事を ",[22,306,307],{},"v-for"," で省略せず全部 ",[22,310,311],{},"NuxtLink"," で描く（",[22,314,315],{},"+N件"," の切り詰めがない）。だから「当月記事は全部 HTML に出る」が day-of-month に依存しない不変条件になる。修正後にスクリプトを単体で回したら EXIT=0 で通った。記録は ",[22,318,319],{},".claude/issues/2026-06-01-verify-blog-payload-month-start-false-positive.md"," に残した。",[10,322,323,324,327],{},"なお ",[22,325,326],{},"_redirects"," も再生成されていた（4804本）。SSG ビルドの副産物で、今回の noindex 論点とは別系統だ。",[14,329,330],{"id":330},"学び",[10,332,333],{},"noindex の meta タグは「載せないでください」というお願いに過ぎない。本当に出したくないものは、検索エンジンに渡す前に、ビルドの段階で成果物から消すのが確実だった。今回は新しく noindex を書く必要がなく、過去の自分が用意した「6経路除外＋本番404」が要件を丸ごと満たしていた。",[10,335,336,337,339],{},"本番にはまだデプロイしていない。次にデプロイするときは、",[22,338,180],{}," が dist に対して緑であることと、unpublished 記事が本番で 404 になることを目で確認してから出す。",[341,342,343],"style",{},"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 .sSkh3, html code.shiki .sSkh3{--shiki-default:#2E8F82;--shiki-dark:#2E8F82}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}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 .sLkVR, html code.shiki .sLkVR{--shiki-default:#AB5E3F;--shiki-dark:#AB5E3F}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}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);}",{"title":50,"searchDepth":82,"depth":82,"links":345},[346,347,348,349,350],{"id":16,"depth":82,"text":17},{"id":166,"depth":82,"text":167},{"id":230,"depth":82,"text":230},{"id":276,"depth":82,"text":277},{"id":330,"depth":82,"text":330},"dev","/blog/unpublished に置いた非公開記事を検索エンジンに載せたくない。noindex meta を入れる前に、本番ビルドから6経路で除外し本番URLを404にする既存実装が要件を満たしていると分かった点検記録。","md",{},true,null,"/unpublished-noindex","blog-platform",false,"2026-06-01T00:00:00.000Z",{"title":5,"description":352},"2026-06/2026-06-01/unpublished-noindex",[364,365,366,367,368],"SEO","noindex","Nuxt","SSG","Cloudflare","-MvjoYCqMXn1ZWtH9qgKZSD6AMI62hHvl4DrFO6LTxk",[],"https://log.eurekapu.com/og/blog/unpublished-noindex.png?v=2026-06-01T00%3A00%3A00.000Z&title=%E9%9D%9E%E5%85%AC%E9%96%8B%E8%A8%98%E4%BA%8B%E4%B8%80%E8%A6%A7%E3%82%92Google%E3%81%AB%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%95%E3%81%9B%E3%81%AA%E3%81%84%E4%BB%95%E7%B5%84%E3%81%BF%E3%82%92%E7%82%B9%E6%A4%9C%E3%81%97%E3%81%9F%E8%A9%B1&author=Kei%20Komatsu&sig=479267f398a75dfa",1782528847462]