インデックスされない なぜを比較|違い・選び方・用途別の最適解
インデックスされない なぜを比較|違い・選び方・用途別の最適解
序盤(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、ステージングはnoindex | curl -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、静的資産は許可、ステージング: 全体noindex | User-agent:* + Disallow制御、X-Robotsで環境分離⁷ |
| robotsシグナル | 重複禁止(meta, header, robots.txtの整合) | 1ソース・オブ・トゥルース² |
| canonical | 200/インデックス可能URLにのみ設定 | ページごとに自己参照が基本 |
| hreflang | 相互/自己参照、x-default明示 | sitemapかHTMLで統一³ |
| HTTP | 200/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、サイトマップの差分配信ジョブ。
診断の初動(推奨順):
- HTTPヘッダ/HTMLのnoindex/canonical/hreflangの衝突をcurlとHTMLダンプで確認。
- ステータスコードをURLサンプルで検査(200/301/404/410、Soft 404の兆候⁵)。
- SSR/SSGの初期HTMLに本文が出力されているかをPuppeteerで確認(Googlebot UA)。
- サイトマップに非正規URL/404が含まれていないか、lastmodが更新されているかを検証。
- ファセット/クエリパラメータの暴走(無限生成)をログの上位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/day | 38,000 | 51,000 | +34% |
| Soft 404率 | 3.8% | 0.6% | -3.2pt |
| JS例外/1k | 5.2 | 0.7 | -87% |
| LCP p75(商品詳細) | 3.6s | 2.4s | -1.2s |
| TTFB p75(HTML) | 620ms | 340ms | -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に含める⁴。
選定フレームワーク(実装手順):
- シグナル単一化: meta robotsはindex固定、環境分離はX-Robots-Tagに集約²⁷。
- SSR/SSGの導入: 収益ページから段階適用、初期HTMLで本文露出⁴。
- HTTP厳密化: 301/302/404/410の設計、ソフト404の監視ダッシュボード⁵。
- サイトマップ差分化: 200/正規URLのみ、lastmod厳密、1日1回以上。
- 監視と回帰防止: 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を対話的に共有する体制づくりを始めましょう。
参考文献
- Barry Schwartz. Google On Discovered - Currently Not Indexed. Search Engine Roundtable. https://www.seroundtable.com/google-on-discovered-currently-not-indexed-37931.html
- Google Search Central Blog. Robots refresher: Page-level controls (2025-03). https://developers.google.com/search/blog/2025/03/robots-refresher-page-level
- Google Search Central Docs. Tell Google about localized versions of your page (hreflang). https://developers.google.com/search/docs/specialty/international/localized-versions
- web.dev. Largest Contentful Paint (LCP). https://web.dev/articles/lcp
- 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
- Google Search Central Docs. Manage crawling for faceted navigation. https://developers.google.com/search/docs/crawling-indexing/crawling-managing-faceted-navigation
- 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