agentgrade

EnglishEspañol日本語中文
← ナレッジベース

「JavaScript レンダリング」とは?

ページが JavaScript レンダリングである、というのは、サーバが返す HTML が実質的に空であることを意味します — 実際のコンテンツ(見出し、本文、商品情報)は、ブラウザが JavaScript バンドルをダウンロードして実行し、React、Vue、Svelte、Angular などのフレームワークを空のルート要素にマウントした後に初めて現れます。

典型的な JS レンダリングのホームページは、ネットワーク上ではこう見えます:

<body>
  <div id="root"></div>
  <script type="module" src="/assets/index-D9LVtTP6.js"></script>
</body>

<body> 内の可視コンテンツはゼロバイトです。ブラウザで見えるすべては、JS が実行された後にクライアント側で構築されたものです。

なぜエージェントにとって重要か

ほとんどの AI クローラは JavaScript を実行しません。ChatGPT、Claude、Perplexity、Gemini のフェッチ、あるいは一般的なエージェントフレームワークが URL にアクセスすると、生の HTML を受け取り — そしてホームページは空です。商品の説明、見出し、リンク、本文はすべて彼らから見えません。

これは情報検索と要約にとって実害があります。LLM があなたの URL を引用しながら「この会社は何をしているか?」と聞かれた場合、ページに読むものがないため、貧弱な回答を生成するか幻覚を起こします。

これが 壊さない もの

HTML の静的な <head> に出力されるタグはクライアントサイドレンダリングの影響を受けません。完全に JS レンダリングの SPA でも、ビルドツールが head を正しく埋めていれば次のチェックは通ります:

サイトが JS レンダリングで さらに これらのチェックも失敗している場合、それらは独立したギャップです — レンダリング自体を直すかどうかに関係なく、ビルドの HTML テンプレートに正しい <head> タグを追加して直してください。

共通の根本原因: 静的ホスティングの SPA バンドル

JS レンダリングのサイトは通常、静的バンドル(index.html と JS/CSS フォルダ)として、どんな GET リクエストに対しても index.html を返すホスト(Vercel 静的、Netlify、Cloudflare Pages、GitHub Pages、S3 + CloudFront、Fastly Frontend、Firebase Hosting)にデプロイされます。

その配信パターンには、リクエストごとのサーバ側ロジックが一切ありません。JS レンダリングの body を生み出すのと同じ設定が、同時に 次も生み出します:

一つの症状(例: コンテンツネゴシエーション)だけを配信モデルに触れずに直すのは通常不可能です — サーバ側ロジックか、静的バンドルの前にあるエッジ関数が必要になります。

修正のはしご

ケースを解決する一番安い選択肢を選んでください。

1. ビルド時のプリレンダリング

ホームページの内容がほぼ静的なら、ビルド中にプリレンダリングします。ブラウザは依然として React/Vue などでハイドレートしますが、サーバが返す最初の HTML には既にレンダリング済みの DOM が含まれます。クローラは本物のコンテンツを見ます。

マーケティングページ、ブログ、ドキュメントには最も労力の少ない修正です。

2. 完全なサーバサイドレンダリング(SSR)

ホームページが動的データ(ログイン状態、A/B テスト、新鮮なコンテンツ)を必要とするなら、リクエストごとにレンダリングするフレームワークを使います:

SSR はランタイム(Node、Bun、Deno、またはエッジランタイム)を必要とし、純粋な静的ホスティングから離れることを意味します。

3. コンテンツネゴシエーションのためのエッジ関数

SSR への移行が大きすぎる場合、静的バンドルの前にエッジ関数を置き、リクエストを傍受して代替表現を返します:

// Cloudflare Worker / Vercel Edge / Netlify Edge
export default async function (request) {
  const accept = request.headers.get('accept') || '';
  const ua = (request.headers.get('user-agent') || '').toLowerCase();
  const isAgent = /claudebot|gptbot|chatgpt-user|perplexitybot|google-extended/.test(ua);

  if (isAgent || accept.includes('text/markdown')) {
    return new Response(await fetchMarkdownSummary(), {
      headers: { 'Content-Type': 'text/markdown', 'Vary': 'Accept, User-Agent' },
    });
  }

  return fetch(request); // 静的バンドルへ通す
}

これは、基盤アプリがクライアントレンダリングのままでなければならない(例: 重いインタラクティブなアプリ)が、それでもエージェントには意味のあるコンテンツを読ませたい場合に適切な修正です。

4. <noscript> フォールバック(最小限)

最低限、静的 HTML 内の <noscript> ブロックに主要なコンテンツを入れます:

<noscript>
  <h1>あなたの会社</h1>
  <p>何をしているか、2 文で。ドキュメント、価格、API へのリンク。</p>
  <a href="/about">概要</a> · <a href="/docs">ドキュメント</a> · <a href="/pricing">価格</a>
</noscript>

これはプリレンダリングの代わりにはなりませんが、クローラが完全に空のページを見るのを止めます。ほとんどの JS レンダリングサイトはこれを持っていません。

確認方法

JavaScript を実行せずにサイトを取得して、ボディに本物のコンテンツがあるかを確認します:

curl -s -A "ClaudeBot/1.0" https://yourdomain.com/ | \
  python3 -c "import sys, re; t = sys.stdin.read(); body = re.search(r'<body[^>]*>(.*?)</body>', t, re.S); print(len(re.sub(r'<[^>]+>', '', body.group(1) if body else '').strip()))"

表示された数字が数百文字未満なら、エージェントはあなたのコンテンツを見ていません。

さらに学ぶ

関連