Article

インデックスされない 原因チートシート【一枚で要点把握】

高田晃太郎
インデックスされない 原因チートシート【一枚で要点把握】

大規模サイトほど「クロール済みなのにインデックスされない」URLが一定割合で発生する。Search Consoleのインデックス ステータスでも「検出—インデックス未登録」や「クロール済み—インデックス未登録」が継続的に計上されるケースは珍しくない。原因は単一ではなく、HTTPシグナル、レンダリング可否、重複判定、内部リンク密度、サーバー健全性が複合的に作用する¹³⁴⁵⁷。本稿はCTO/技術リードが短時間で要点を把握し、実装で潰し込みできるよう、一次診断のチートシート、ガードレール実装、計測とベンチマーク、ROI試算までを一枚に整理する。

インデックスされない主要因チートシート(一次診断)

まずは機械的に切り分ける。HTTPとHTML/レンダリング、サイト構造、ポリシーの4層で確認する。

代表的な原因観測ポイント対処
HTTP4xx/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 allnoindexは(再クロール後に)検索結果から除外²
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 html

URLS = [ ‘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のカバレッジで「クロール済み—インデックス未登録」が多い場合、以下の順で潰し込みが有効。

  1. サーバーログで対象URLのHTTPコードと応答時間を抽出(5xx/429/200の割合)。ネットワーク/HTTPエラーの把握が起点¹。
  2. HTMLソースでnoindex/canonical/alt-hreflangの整合性を確認²⁷⁹。
  3. 内部リンク距離(Click Depth)を可視化し、3以上は構造改善。体系的なリンク設計で重要ページへ到達しやすくする⁵。
  4. サイトマップに新規URLが載っているか、更新頻度とサイズ上限(50,000URL/1ファイル、50MB未圧縮)を順守⁸。
  5. JavaScript依存ページはSSR/SSGに寄せ、主要コンテンツを初期HTMLに含める³。

なお、Google Indexing APIは一般のウェブページには非対応(現在はJobPostingとBroadcastEvent onVideoの一部のみ対象)。インデックス促進手段としての常用は不適切で、サイトマップと内部リンクが基本線となる⁶。

導入手順とROI試算

以下の手順で2週間以内に安定ラインに載せるのが現実的だ。

  1. 環境分離の徹底:robots.txtとX-Robots-Tagの環境分岐を導入(コード1/2)²。
  2. HTTP正規化:404/410/503の正確な返却、200の誤用をCIで検出(コード3/6)¹。
  3. サイトマップ刷新:ストリーミング+gzip、10万URL単位のスループットを確保(コード4/8)⁸。
  4. 正規化一貫性:canonical/JSON-LD/OGのURL表記を統一(コード5)⁷。
  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徹底とサイトマップのストリーミング化から着手し、ベンチマークを取りながら継続的に最適化していこう⁴。

参考文献

  1. Google Search Central. HTTP とネットワーク関連の問題: https://developers.google.com/search/docs/crawling-indexing/http-network-errors
  2. Google Search Central. robots メタタグ、data-nosnippet、X-Robots-Tag: https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag
  3. Google Search Central. JavaScript SEO の基本: https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics
  4. Google Search Central. 大規模サイトのクロール予算を管理する: https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget
  5. Google Search Central Blog. サイトのリンク構造の重要性: https://developers.google.com/search/blog/2008/10/importance-of-link-architecture
  6. Google Search Central. Indexing API の使用方法: https://developers.google.com/search/apis/indexing-api/v3/using-api
  7. Google Search Central. 重複する URL を統合(canonical の活用): https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls
  8. Google Search Central. サイトマップの作成と送信(サイズ・圧縮のガイドライン含む): https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap
  9. Google Search Central. 多言語・多地域サイトの構築(hreflang): https://developers.google.com/search/docs/specialty/international/localized-versions