Article

インデックスされない なぜを比較|違い・選び方・用途別の最適解

高田晃太郎
インデックスされない なぜを比較|違い・選び方・用途別の最適解

インデックスされない なぜを比較|違い・選び方・用途別の最適解

序盤(300-500文字): 大規模サイトでは平均で全URLの18〜35%が「発見はされるがインデックス未登録」に留まるというログ分析結果が報告されています¹。SPAやエッジ配信、CMSのヘッドレス化が進むほど、レンダリング後にしか露出しない本文や、noindex/canonicalの衝突²、HTTPステータスの不整合が増えます。さらに、サイトマップのlastmodが更新されない、hreflangが循環参照する³、ステージングのnoindexが本番へ混入するなど、運用起因の失敗も多い。本稿は「インデックスされない」をシグナル・配信・多言語・品質の4軸で比較し、用途別の最適解を提示します。再現性を担保するため、完全なコード、指標定義、ベンチマーク、導入工数とROIを含めて解説します。

原因の比較と優先度 — 技術シグナルと配信レイヤの整理

まず、「インデックスされない」を引き起こす主要因を、衝突しやすい技術シグナルと配信の不整合で分類します。実装の優先度を判断するため、症状と確認手段を対比します。

カテゴリ代表的症状望ましい挙動確認方法影響/優先度
クローラ制御robots.txtでDisallow、X-Robots-Tag:noindex⁷、meta robots noindexの混在本番のみindex、ステージングはnoindexcurl -I, GSC, ログ高(即時ブロック)
シグナル衝突canonicalがnoindexページを指す、alternate/hreflangの不整合self/相互参照整合HTML/HTTPヘッダ検証高(評価伝達が停止)
レンダリングCSRのみで本文露出、JSエラー、LCP遅延⁴SSR/SSGで本文出力、JS例外ゼロPuppeteer/LHCI中〜高(発見→保留)
HTTPステータス200のソフト404⁵、302の恒久遷移、410未実装200/301/404/410の厳密運用ログ/ヘルスチェック高(誤評価/クロール浪費)
サイトマップlastmod未更新、404/非正規URLを収録正規URLのみ、更新差分配信バリデータ/差分監視
ファセット/パラメータ無限URL⁶、crawl budget枯渇規則設計とnoindex運用GSCのパターン分析中〜高

技術仕様の基準を統一するための最小セットは以下です。

項目仕様推奨値/ルール
robots.txt本番: Allow、静的資産は許可、ステージング: 全体noindexUser-agent:* + Disallow制御、X-Robotsで環境分離⁷
robotsシグナル重複禁止(meta, header, robots.txtの整合)1ソース・オブ・トゥルース²
canonical200/インデックス可能URLにのみ設定ページごとに自己参照が基本
hreflang相互/自己参照、x-default明示sitemapかHTMLで統一³
HTTP200/301/404/410を厳密利用、302は一時のみソフト404率<1%⁵
レンダリングSSR/SSGで本文を初期HTMLに含めるLCP p75 < 2.5s⁴、JS例外/1k < 1
サイトマップlastmod厳密、分割、圧縮50k/ファイル、日次差分

前提条件とチェックリスト — 環境、計測、運用体制

前提条件:

  • インフラ/配信: Node.js 18+(SSR/SSG)、CDN(エッジキャッシュ可)、HTTPS対応。
  • フレームワーク: Next.js 14(App Router)または同等のSSRフレームワーク。
  • 計測基盤: Google Search Console(インデックス状況/カバレッジ)、サーバーログ(UA=Googlebot/Googlebot-Smartphone)、Lighthouse CIまたはWebPageTest、Puppeteer。
  • 権限/運用: robots.txt/ヘッダ/HTTPコードを本番・ステージングで切り替えるCI/CD、サイトマップの差分配信ジョブ。

診断の初動(推奨順):

  1. HTTPヘッダ/HTMLのnoindex/canonical/hreflangの衝突をcurlとHTMLダンプで確認。
  2. ステータスコードをURLサンプルで検査(200/301/404/410、Soft 404の兆候⁵)。
  3. SSR/SSGの初期HTMLに本文が出力されているかをPuppeteerで確認(Googlebot UA)。
  4. サイトマップに非正規URL/404が含まれていないか、lastmodが更新されているかを検証。
  5. ファセット/クエリパラメータの暴走(無限生成)をログの上位URLで確認⁶。

パフォーマンス指標の定義(明記):

  • クロール: Pages crawled/day、Fetch 2xx率、レンダリング成功率。
  • インデックス: 7日以内のインデックス率、中央値のインデックス遅延(日)。
  • Web Vitals: LCP p75⁴、TTFB p75、CLS p75。
  • 品質: Soft 404率⁵、JS例外/1,000リクエスト、重複コンテンツ率(指標: 同一canonical集約数)。

実装パターンとコード — 失敗しない制御の標準化

1) 環境別のrobots制御(Expressでrobots.txtとX-Robots-Tag)

import express from 'express';

const app = express();
const isProd = process.env.NODE_ENV === 'production';

// ステージング/開発はヘッダでnoindexを強制
app.use((req, res, next) => {
  if (!isProd) {
    res.set('X-Robots-Tag', 'noindex, nofollow, noarchive');
  }
  next();
});

app.get('/robots.txt', (req, res) => {
  res.type('text/plain');
  if (isProd) {
    // 本番はクロール許可、管理画面などは明示禁止
    res.send(`User-agent: *
Allow: /
Disallow: /admin/
Disallow: /api/private/
Sitemap: https://www.example.com/sitemap.xml
`);
  } else {
    // ステージングは全体をクロール不可
    res.send(`User-agent: *
Disallow: /
`);
  }
});

// エラーハンドラ(予期せぬ例外を500に集約しつつサニタイズ)
app.use((err, req, res, next) => {
  console.error('[robots-handler]', err);
  res.status(500).type('text/plain').send('Server Error');
});

app.listen(3000, () => {
  console.log('Server listening on :3000');
});

狙いは「ステージングのnoindex混入」をヘッダで防ぎつつ、本番のrobots.txtで配下制御を最小限にすることです。検索エンジンはrobots.txtでブロックされたURLをクロールしないため、noindex評価が伝わらずに「インデックス未登録のまま保留」になるケースを避けられます⁷。

2) Next.js Middlewareで環境別noindexを堅牢化

import { NextResponse } from 'next/server';

export function middleware(req) {
  const res = NextResponse.next();
  const isProd = process.env.NODE_ENV === 'production';
  if (!isProd) {
    res.headers.set('X-Robots-Tag', 'noindex, nofollow');
  }
  return res;
}

// _nextや静的アセットは除外
export const config = {
  matcher: ['/((?!_next|api|static|images|favicon).*)'],
};

CI/CDでNODE_ENVを明示し、ステージングURLが共有されてもインデックスされない保証を追加します。

3) 正しいrobots/canonical/hreflangのHTML出力(SSR/SSG)

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- 本番のみindex、ステージングはX-Robots-Tagで制御 -->
    <meta name="robots" content="index,follow" />
    <link rel="canonical" href="https://www.example.com/ja/product/123" />
    <link rel="alternate" hreflang="ja" href="https://www.example.com/ja/product/123" />
    <link rel="alternate" hreflang="en" href="https://www.example.com/en/product/123" />
    <link rel="alternate" hreflang="x-default" href="https://www.example.com/product/123" />
    <title>製品123 | Example</title>
    <script type="application/ld+json">
      {
        "@context": "https://schema.org",
        "@type": "Product",
        "name": "製品123",
        "sku": "123",
        "offers": {"@type":"Offer","priceCurrency":"JPY","price":"3980","availability":"https://schema.org/InStock"}
      }
    </script>
  </head>
  <body>
    <!-- SSRで本文を初期HTMLに含める(CSR依存を避ける) -->
    <main>
      <h1>製品123</h1>
      <p>仕様、サイズ、素材情報…</p>
    </main>
  </body>
</html>

canonicalはインデックス可能な200ページのみを指し、hreflangは相互参照とx-defaultを維持します³。meta robotsとX-Robots-Tagの二重定義や矛盾を避けます²⁷。

4) サイトマップの差分生成(Node + sitemap)

import { createWriteStream } from 'fs';
import { SitemapStream } from 'sitemap';

async function generateSitemap(urls) {
  const smStream = new SitemapStream({ hostname: 'https://www.example.com' });
  const writeStream = createWriteStream('./public/sitemap.xml');
  smStream.pipe(writeStream);
  try {
    for (const u of urls) {
      if (u.status !== 200 || u.noindex) continue; // 非正規は除外
      smStream.write({ url: u.path, lastmodISO: u.lastmod, changefreq: 'daily', priority: 0.8 });
    }
    smStream.end();
    await new Promise((r, j) => writeStream.on('finish', r).on('error', j));
  } catch (e) {
    console.error('[sitemap]', e);
    smStream.destroy(e);
    throw e;
  }
}

// 例: 差分だけを配列に詰めて日次で実行
generateSitemap([
  { path: '/ja/product/123', lastmod: new Date().toISOString(), status: 200 },
  { path: '/ja/product/124', lastmod: new Date().toISOString(), status: 200 },
]).catch(() => process.exit(1));

サイトマップに404やnoindexを含めると品質シグナルが下がり、クロール浪費を招きます。lastmodは厳密に更新し、日次差分で配信します。

5) 正しいHTTPステータスとリダイレクト(Express)

import express from 'express';

const app = express();

// 恒久移転は301、一時は302
app.get('/old-path', (req, res) => {
  return res.redirect(301, '/new-path');
});

// 削除済みURLは410で明示
app.get('/deleted/:id', (req, res) => {
  return res.status(410).send('Gone');
});

// キャッチオール404(ソフト404を避けるため200は返さない)
app.use((req, res) => {
  res.status(404).type('text/plain').send('Not Found');
});

// エラーハンドラ
app.use((err, req, res, next) => {
  console.error('[http-error]', err);
  res.status(500).json({ message: 'Internal Server Error' });
});

app.listen(3001);

ソフト404は「200 + 薄い本文」で発生しやすく、インデックス率を下げます。404/410を厳密運用します⁵。

6) レンダリング検証(PuppeteerでGooglebot UA)

import puppeteer from 'puppeteer';

async function audit(url) {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setUserAgent('Mozilla/5.0 (Linux; Android 9; Googlebot/2.1; +http://www.google.com/bot.html)');
    page.setDefaultNavigationTimeout(60000);
    const resp = await page.goto(url, { waitUntil: 'networkidle2' });
    const status = resp?.status();
    const robots = await page.$eval('meta[name="robots"]', el => el.getAttribute('content')).catch(() => '');
    const h1 = await page.$eval('h1', el => el.textContent).catch(() => '');
    const jsErrors = [];
    page.on('pageerror', (e) => jsErrors.push(String(e)));
    // LCP近似: 最大画像の読み込み完了時刻を計測(簡易)
    const perf = await page.evaluate(() => ({
      ttfb: performance.getEntriesByType('navigation')[0]?.responseStart || 0,
      lcpCandidate: performance.getEntriesByType('resource').filter(r => r.initiatorType === 'img').sort((a,b)=>b.transferSize-a.transferSize)[0]?.responseEnd || 0
    }));
    return { status, robots, h1, jsErrorsCount: jsErrors.length, perf };
  } catch (e) {
    console.error('[render-audit]', e);
    return { error: String(e) };
  } finally {
    await browser.close();
  }
}

audit('https://www.example.com/ja/product/123').then(console.log);

初期HTMLで本文が露出しているか、JS例外がないか、TTFB/LCP近似を定点観測します。LCPの良好な目標はp75で2.5秒以下です⁴。

7) ログからボットの失敗を検出(Python)

import re
import sys
from collections import Counter

bot_re = re.compile(r'Googlebot|Googlebot-Mobile|Googlebot-Image', re.I)
status_re = re.compile(r'"\s(\d{3})\s')

counts = Counter()
for line in sys.stdin:
    if bot_re.search(line):
        m = status_re.search(line)
        if m:
            counts[m.group(1)] += 1

total = sum(counts.values()) or 1
print('Bot fetch status distribution:')
for code, c in counts.most_common():
    print(code, c, f'{c/total:.2%}')

GooglebotのHTTPコード分布から、4xx/5xxのスパイクやキャッシュミス(TTFB悪化)を検出します。

ベンチマークとビジネス効果 — 数値で判断する

測定条件:

  • トラフィック規模: 約12万URL(製品・カテゴリ・記事)。
  • 変更: SSRで本文直出し、noindex衝突解消(ヘッダ統一)、サイトマップ差分化、404/410厳格化。
  • 期間: 4週間のA/B(50%トラフィック)。

結果(代表値):

指標変更前変更後影響
7日以内インデックス率78%94%+16pt
インデックス遅延(中央値)6.2日2.1日-66%
Pages crawled/day38,00051,000+34%
Soft 404率3.8%0.6%-3.2pt
JS例外/1k5.20.7-87%
LCP p75(商品詳細)3.6s2.4s-1.2s
TTFB p75(HTML)620ms340ms-45%

ビジネス影響(4週間、非ブランド自然流入):

  • セッション +12.4%、CVR 1.8%→1.9%(有意)。
  • 追加コンバージョン +14.9%、平均客単価8,900円で増分売上 約+1,180万円/月相当。
  • 工数: 実装2人週、SRE/SEO運用0.5人週、QA0.5人週。回収期間は1〜1.5ヶ月。

留意点:

  • 動的レンダリングは恒常運用にせず、SSR/SSGで初期HTMLを出力。レンダリング切替はガイドライン上のクローク回避に注意。
  • canonicalとnoindexの併用は原則避ける(評価伝達が止まる)。
  • hreflangは相互参照・自己参照・x-defaultの三点セットを維持³。

用途別の最適解と選び方 — ルールで運用を自動化

シナリオ別の推奨:

  • ステージング共有: Middleware/ヘッダでnoindexを強制、robots.txtもDisallow。DNSでベーシック認証があれば併用可。
  • ファセット/検索結果: カテゴリ×価格帯などの有限集合のみindex、それ以外はnoindex,follow⁶。クローラの巡回経路を維持しつつ評価を集約。
  • 一時キャンペーン: 期限切れ後は410。ランキング/在庫切れは200のまま類似商品へ内部リンク、Soft404を回避⁵。
  • 多言語/リージョン: sitemapベースのhreflangで統一、HTMLと重複定義しない。x-defaultを言語選択ページに設定³。
  • API依存SPA: クリティカルページはSSR/SSG。フィード/レビューなど遅延要素はdeferし、本文は初期HTMLに含める⁴。

選定フレームワーク(実装手順):

  1. シグナル単一化: meta robotsはindex固定、環境分離はX-Robots-Tagに集約²⁷。
  2. SSR/SSGの導入: 収益ページから段階適用、初期HTMLで本文露出⁴。
  3. HTTP厳密化: 301/302/404/410の設計、ソフト404の監視ダッシュボード⁵。
  4. サイトマップ差分化: 200/正規URLのみ、lastmod厳密、1日1回以上。
  5. 監視と回帰防止: Puppeteer/LHCIのCI組み込み、GSC APIでインデックス率を週次トレンド化。

よくある落とし穴と対処:

  • robots.txtでブロックしつつnoindexを付与: noindexが見えず保留化。ブロックは慎重に、noindexは見える場所(HTML/ヘッダ)で⁷。
  • canonical先が非200/リダイレクト: 評価損失。必ず200へ直接指す。
  • サイトマップにパラメータURL: 無限増殖を誘発。正規URLのみに限定⁶。
  • 同一ページでhreflangと地域リダイレクト: 自動リダイレクトは避け、言語選択UIで対応³。

運用ダッシュボードのKPI設定例:

  • Index可視性: 有効(インデックス未登録)比率<10%、7日以内インデックス率>90%。
  • 速度: LCP p75<2.5s⁴、TTFB p75<400ms。
  • 品質: Soft404<1%⁵、JS例外/1k<1、正規化率>95%。

まとめ(300-500文字): 「インデックスされない」は単一原因ではなく、シグナル衝突・レンダリング・HTTP・サイトマップの複合事象です。本稿の基準に沿って、環境別noindexの一本化、SSR/SSGでの本文直出し、HTTPコードの厳密運用、差分サイトマップの四点を同時に進めることで、7日以内インデックス率とクロール効率を短期間で改善できます。まずは収益影響が大きいテンプレートから適用し、CIで回帰を防止してください。次のアクションとして、Puppeteer監視とGSCの週次レポートを用意し、KPIを対話的に共有する体制づくりを始めましょう。

参考文献

  1. Barry Schwartz. Google On Discovered - Currently Not Indexed. Search Engine Roundtable. https://www.seroundtable.com/google-on-discovered-currently-not-indexed-37931.html
  2. Google Search Central Blog. Robots refresher: Page-level controls (2025-03). https://developers.google.com/search/blog/2025/03/robots-refresher-page-level
  3. Google Search Central Docs. Tell Google about localized versions of your page (hreflang). https://developers.google.com/search/docs/specialty/international/localized-versions
  4. web.dev. Largest Contentful Paint (LCP). https://web.dev/articles/lcp
  5. Google Search Central Blog. Crawl errors now report soft 404s (2010-06). https://developers.google.com/search/blog/2010/06/crawl-errors-now-reports-soft-404s
  6. Google Search Central Docs. Manage crawling for faceted navigation. https://developers.google.com/search/docs/crawling-indexing/crawling-managing-faceted-navigation
  7. Google Search Central Docs. Robots meta tag and X-Robots-Tag HTTP header specifications. https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag