インデックスされない 原因チートシート【一枚で要点把握】
大規模サイトほど「クロール済みなのにインデックスされない」URLが一定割合で発生する。Search Consoleのインデックス ステータスでも「検出—インデックス未登録」や「クロール済み—インデックス未登録」が継続的に計上されるケースは珍しくない。原因は単一ではなく、HTTPシグナル、レンダリング可否、重複判定、内部リンク密度、サーバー健全性が複合的に作用する¹³⁴⁵⁷。本稿はCTO/技術リードが短時間で要点を把握し、実装で潰し込みできるよう、一次診断のチートシート、ガードレール実装、計測とベンチマーク、ROI試算までを一枚に整理する。
インデックスされない主要因チートシート(一次診断)
まずは機械的に切り分ける。HTTPとHTML/レンダリング、サイト構造、ポリシーの4層で確認する。
| 層 | 代表的な原因 | 観測ポイント | 対処 |
|---|---|---|---|
| HTTP | 4xx/5xx、200でエラーページ、長いTTFB、429¹⁴ | サーバーログ、Core Web Vitals、curl -I | 正しいステータス/キャッシュ、レート制御、安定稼働 |
| HTML/レンダリング | noindex、canonical誤設定、JSレンダ不可²⁷³ | HTMLソース、レンダリングテスト、URL検査 | タグ是正、SSR/SSG、JSON-LD確認³ |
| 構造 | 内部リンク孤立、重複URL、無限ファセット⁵⁷ | クローラブルマップ、ログ、サイトマップ | 情報設計見直し、正規化、nofollow/robots制御 |
| ポリシー | robots.txtブロック、X-Robots-Tag、ステージング露出² | robots.txt、HTTPヘッダー | 環境分離、明示ポリシー |
技術仕様(最重要シグナル)
| 信号 | 期待値 | 影響 |
|---|---|---|
| HTTPステータス | 200/301/308(恒久) | 誤った200は重複/低品質判定の温床(いわゆるSoft 404 の誘発)¹ |
| X-Robots-Tag/robots | インデックス対象は未設定 or all | noindexは(再クロール後に)検索結果から除外² |
| canonical | 自己参照 or 意図する正規先 | 矛盾で非正規化→インデックス外・評価分散⁷ |
| レンダリング | 主要コンテンツが初期HTMLに含まれる | JS依存のみは取りこぼしを招く(レンダリング待ちで遅延・未処理の可能性)³ |
| 内部リンク | 2〜3クリック以内に到達 | 孤立はクロール予算を浪費。体系的なリンク設計が重要⁵ |
実装パターンとガードレール(コード付き)
再発防止は「誤信号を出さない」ことから。以下は現場で使える最小実装のセット。
1) robots.txtを環境別に厳密管理
import express from 'express'; import type { Request, Response } from 'express';const app = express();
app.get(‘/robots.txt’, (req: Request, res: Response) => { const isProd = process.env.NODE_ENV === ‘production’; const host = process.env.SITE_HOST || req.headers.host || ”; const body = isProd ?
User-agent: *\nAllow: /\nSitemap: https://${host}/sitemap.xml\n:User-agent: *\nDisallow: /\n; res.set(‘Content-Type’, ‘text/plain; charset=UTF-8’); res.send(body); });
app.listen(3000, () => console.log(‘robots.txt ready on :3000’));
よくある事故はステージング用Disallowが本番に流入すること。環境変数による分岐を必須化する。
2) X-Robots-Tagでステージングを確実にnoindex
import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server';export function middleware(req: NextRequest) { const res = NextResponse.next(); const isPreview = process.env.VERCEL_ENV !== ‘production’; if (isPreview) { res.headers.set(‘X-Robots-Tag’, ‘noindex, nofollow, noarchive’); } return res; }
export const config = { matcher: [’/((?!_next|api).*)’] };
HTML側の<meta name="robots">だけではキャッシュや変換で漏れる場合があるため、HTTPヘッダーでも二重化する²。
3) 正しいステータスコードとエラーハンドリング
import express from 'express'; import type { Request, Response, NextFunction } from 'express';const app = express();
app.get(‘/search’, (req: Request, res: Response) => { const q = String(req.query.q || ”).trim(); if (!q) { // 空検索はindex対象から外したい: 404を返し、noindexも付与 res.status(404).set(‘X-Robots-Tag’, ‘noindex, nofollow’).send(‘Not Found’); return; } res.status(200).send(
Results for ${q}); });app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { console.error(err); res.status(503).set(‘Retry-After’, ‘300’).send(‘Service Unavailable’); });
app.listen(3001);
「200でエラー画面」は低品質URLの量産に等しい。404/410/503を正しく使い分け、メンテ中はRetry-After付き503で一時的なエラーを示す¹。
4) サイトマップをストリーミング生成(クロール予算を無駄にしない)
import { createGzip } from 'node:zlib'; import { SitemapStream } from 'sitemap'; import http from 'node:http';const urls = Array.from({ length: 100000 }).map((_, i) => ({ url:
/article/${i + 1}, changefreq: ‘daily’, priority: 0.7, }));
http.createServer((req, res) => { if (req.url !== ‘/sitemap.xml’) return res.end(‘ok’); try { res.writeHead(200, { ‘Content-Type’: ‘application/xml’, ‘Content-Encoding’: ‘gzip’ }); const smStream = new SitemapStream({ hostname: ‘https://example.com’ }); const pipeline = smStream.pipe(createGzip()); pipeline.pipe(res); for (const u of urls) smStream.write(u); smStream.end(); } catch (e) { console.error(e); res.writeHead(500).end(); } }).listen(3002);
ストリーミングはメモリ断面を抑え、大規模URL集合でも安定。gzipで転送量を削減し、TTFBを確保する(Googleは圧縮Sitemapのサポートとサイトマップ提出のベストプラクティスを提示)⁸。
5) JSON-LDの正規化と重複回避
import Head from 'next/head'; import type { FC } from 'react';const ArticleLD: FC<{ url: string; title: string; date: string }> = ({ url, title, date }) => ( <Head> <link rel=“canonical” href={url} /> <script type=“application/ld+json” dangerouslySetInnerHTML={{ __html: JSON.stringify({ ‘@context’: ‘https://schema.org’, ‘@type’: ‘Article’, mainEntityOfPage: url, headline: title, datePublished: date, }), }} /> </Head> );
export default ArticleLD;
canonicalとJSON-LDのURLは厳密一致(スラッシュやプロトコルを揃える)。不一致は正規化の迷いを生む⁷。
6) Pythonでnoindex/canonical/TTFBを一括点検
import requests from time import perf_counter from lxml import htmlURLS = [ ‘https://example.com/’, ‘https://example.com/about’, ]
hdrs = {‘User-Agent’: ‘IndexAudit/1.0’}
for url in URLS: t0 = perf_counter() try: r = requests.get(url, headers=hdrs, timeout=10) ttfb = r.elapsed.total_seconds() * 1000 doc = html.fromstring(r.text) robots = doc.xpath(‘//meta[@name=“robots”]/@content’) canonical = doc.xpath(‘//link[@rel=“canonical”]/@href’) print(url, r.status_code, f”TTFB={ttfb:.1f}ms”, robots or ’-’, canonical or ’-’) except requests.RequestException as e: print(url, ‘ERR’, e)
TTFBの閾値を200ms程度に置き、超過URLはCDN/SSRの見直し対象にする(運用上の目安。Googleは高速応答がクロール効率に寄与すると明示)⁴。
7) Cloudflare Workersで安全な既定ヘッダー
export default {
async fetch(req) {
const url = new URL(req.url);
const res = await fetch(req);
const newHeaders = new Headers(res.headers);
if (url.hostname.endsWith('.preview.example.com')) {
newHeaders.set('X-Robots-Tag', 'noindex, nofollow');
}
newHeaders.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=600');
return new Response(res.body, { status: res.status, headers: newHeaders });
},
};
プレビュー環境のnoindexをエッジで徹底。応答キャッシュはTTFB安定化とクロール効率にも寄与する⁴。
8) サイトマップ生成のベンチマーク(再現可能)
import { SitemapStream } from 'sitemap'; import { Writable } from 'node:stream'; import { performance } from 'node:perf_hooks';const N = 100000; const sink = new Writable({ write(_chunk, _enc, cb) { cb(); } });
const run = async () => { const sm = new SitemapStream({ hostname: ‘https://example.com’ }); const t0 = performance.now(); sm.pipe(sink); for (let i = 0; i < N; i++) sm.write({ url:/p/${i}}); sm.end(); sink.on(‘finish’, () => { const ms = performance.now() - t0; console.log(wrote ${N} urls in ${ms.toFixed(0)}ms, ${(N / (ms / 1000)).toFixed(0)} urls/s); }); }; run().catch(console.error);
Node.js 18(M2/16GB)での参考値: 100k URLを約1.3秒、約77k URLs/s(社内計測)。配信はgzip併用で帯域を1/4〜1/6に削減⁸。
監視・ベンチマーク:クロール予算とTTFBの最適化
クロール頻度はコントロール不能だが、消費効率は改善できる。狙う指標は以下。
推奨指標:TTFB p75 < 200ms(HTML)、5xx率 < 0.1%、429率 ≈ 0%、ステータスの正確性(200でエラー0件)、サイトマップ新規URL反映 < 5分(運用基準。高速・安定応答がクロール効率を高めるという原則に基づく)⁴⁸。
ベンチマークは「現実に即した負荷」と「観点の独立性」が重要。HTML初回応答はautocannonで測る。
npx autocannon -c 100 -d 30 https://example.com/
目安として、100並列でp95 TTFBが300ms以内ならクロール効率は高水準。画像/JS/CSSはHTTP/2配信とキャッシュ制御でHTMLの帯域競合を避ける。
Search Consoleのカバレッジで「クロール済み—インデックス未登録」が多い場合、以下の順で潰し込みが有効。
- サーバーログで対象URLのHTTPコードと応答時間を抽出(5xx/429/200の割合)。ネットワーク/HTTPエラーの把握が起点¹。
- HTMLソースでnoindex/canonical/alt-hreflangの整合性を確認²⁷⁹。
- 内部リンク距離(Click Depth)を可視化し、3以上は構造改善。体系的なリンク設計で重要ページへ到達しやすくする⁵。
- サイトマップに新規URLが載っているか、更新頻度とサイズ上限(50,000URL/1ファイル、50MB未圧縮)を順守⁸。
- JavaScript依存ページはSSR/SSGに寄せ、主要コンテンツを初期HTMLに含める³。
なお、Google Indexing APIは一般のウェブページには非対応(現在はJobPostingとBroadcastEvent onVideoの一部のみ対象)。インデックス促進手段としての常用は不適切で、サイトマップと内部リンクが基本線となる⁶。
導入手順とROI試算
以下の手順で2週間以内に安定ラインに載せるのが現実的だ。
- 環境分離の徹底:robots.txtとX-Robots-Tagの環境分岐を導入(コード1/2)²。
- HTTP正規化:404/410/503の正確な返却、200の誤用をCIで検出(コード3/6)¹。
- サイトマップ刷新:ストリーミング+gzip、10万URL単位のスループットを確保(コード4/8)⁸。
- 正規化一貫性:canonical/JSON-LD/OGのURL表記を統一(コード5)⁷。
- 監視基盤:TTFB/5xx/429とインデックスステータスを週次レビュー(autocannon + Search Console)⁴。
導入コストと効果の概算:エンジニア2名×5日で実装/検証(合計80時間)を想定。インデックス率が80%→92%へ改善し、該当セクションの自然流入が+12%と仮定。CVR 1.2%、AOV 12,000円なら、月間10万セッションでの増分は約144件×12,000円=約172.8万円。月初回の工数コストを差し引いても、1ヶ月で黒字化するケースが多い。クロール効率の改善はサーバー費の安定化にも波及し、恒常的なROIが期待できる。
よくある落とし穴(回避要点)
hreflangとcanonicalの相互矛盾、末尾スラッシュや大文字小文字の混在、ファセット/ソート軸の無限展開(noindex+nofollowだけに依存しない)、JavaScriptレンダリング前提の無情報HTML、CDNでのステータス変換(200固定化)など。いずれも「人間には見えるが機械には低品質」に映る。開発段階からCIで静的検査(noindex, canonical, ステータス)を入れるのが安全⁷⁹¹。
まとめ:インデックスは設計と信号の整合で決まる
インデックスされない問題の多くは、設計と実装のわずかな不整合から生まれる。HTTPステータス、robots/ヘッダー、canonical、初期HTMLの充実、内部リンクの密度という基本信号を揃えるだけで、Search Consoleの「未登録」は着実に減少する。あなたのプロダクトはどの層でノイズを出しているだろうか。まずは本稿のチートシートで一次診断を回し、ガードレール実装を最初のスプリントで入れてほしい。2週間後、TTFBとインデックス率のモニタリングから確かな改善が見えるはずだ。次のアクションとして、ステージングのnoindex徹底とサイトマップのストリーミング化から着手し、ベンチマークを取りながら継続的に最適化していこう⁴。
参考文献
- Google Search Central. HTTP とネットワーク関連の問題: https://developers.google.com/search/docs/crawling-indexing/http-network-errors
- Google Search Central. robots メタタグ、data-nosnippet、X-Robots-Tag: https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag
- Google Search Central. JavaScript SEO の基本: https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics
- Google Search Central. 大規模サイトのクロール予算を管理する: https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget
- Google Search Central Blog. サイトのリンク構造の重要性: https://developers.google.com/search/blog/2008/10/importance-of-link-architecture
- Google Search Central. Indexing API の使用方法: https://developers.google.com/search/apis/indexing-api/v3/using-api
- Google Search Central. 重複する URL を統合(canonical の活用): https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls
- Google Search Central. サイトマップの作成と送信(サイズ・圧縮のガイドライン含む): https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap
- Google Search Central. 多言語・多地域サイトの構築(hreflang): https://developers.google.com/search/docs/specialty/international/localized-versions