BLOG

自社サイトが生成AIから見えていなかった — SPAの実測と直し方

SPA は JavaScript を実行しない相手には空のHTMLしか返さない。当社サイトで実測し、Node 上の事前描画で直した記録。

公開 ・タウンDX総研

この記事の要点

  • Vue で作った当社サイトは、JavaScript を実行しない相手からは 全ページが同じ空のHTML(本文0文字)に見えていました。
  • ビルド時に全ページを描画して静的HTMLとして書き出す「事前描画」で直しました。 Vercel ではブラウザを使う方式が動かず、Node だけで描画する方式に切り替えています。
  • 作業の途中で、構造化データ(JSON-LD)が3週間壊れたままだったことにも 気づきました。今はビルドのたびに検査しています。

何が起きていたのか

当社のサイトは Vue 3 で作った SPA(シングルページアプリケーション)です。 ブラウザが最初に受け取るのは、ほぼ空の HTML と JavaScript だけで、 本文・ページの題名・説明文は、JavaScript が動いてから組み立てられます。

人がブラウザで見る分には問題ありません。しかし、取得した HTML をそのまま読み、 JavaScript を実行しない相手からは、中身の無いページに見えます。

2026年10月18日、User-Agent を GPTBot にして、curl で各ページを取得しました。 curl は JavaScript を実行しないので、そうした相手に返っている HTML がそのまま見えます。

ページHTMLの大きさ題名本文正規URL
/faq6,965 バイトトップページのもの0文字なし
/event-portal6,965 バイトトップページのもの0文字なし
/tottori-ai-study6,965 バイトトップページのもの0文字なし

16ページすべてが、1バイトも違わない同じ HTML を返していました。 よくある質問のページを読みに来ても、題名はトップページのもので、質問も答えもどこにもありません。

なぜ Google では困っていなかったのか

Google の検索クローラーは、JavaScript を実行してから内容を読みます。 そのため検索結果では、各ページが正しい題名で表示されていました。 「検索に出ているから大丈夫」と思えてしまうのが、この問題の気づきにくいところです。

一方で、生成AIのサービスなどが使うクローラーには、取得した HTML をそのまま読むものがあります。 そうした相手にとって、当社サイトは実質的に存在していませんでした。

直し方:ビルド時に全ページを描画しておく

方針は単純で、サイトを公開する前(ビルド時)に全ページを一度描画し、 その結果をページごとの HTML ファイルとして置いておきます。 最初から中身の入った HTML が返るので、JavaScript を実行しない相手にも本文が届きます。 人がブラウザで開いたときは、これまでどおり JavaScript が画面を描き直します。

最初の方式は Vercel で動かなかった

はじめは、ビルド中に本物のブラウザ(Chromium)を起動してページを開き、 表示された内容を保存する方式にしました。手元では問題なく動きました。 ところが、当社が使っている Vercel のビルド環境では、次のようになりました。

試したこと結果
Chromium と必要な OS の部品を入れてからビルドビルドが失敗し、12分待っても公開されない
その手順を外す1分で公開された(原因が確定)
Chromium 本体だけを入れるビルドは通るが、Chromium を起動できず事前描画がとばされた

ビルド環境にブラウザを持ち込む方式は、環境に左右されます。 そこで、ブラウザを使わず、Node の上で Vue を描画して HTML を作る方式(サーバーサイドレンダリング)に切り替えました。 これならビルド環境を選びません。

事前描画で気をつけた3つのこと

  • 画面に入るとふわっと表示される演出。 表示前の要素は透明(opacity: 0)にしてあるため、そのまま書き出すと 「本文はあるが透明」な HTML になります。事前描画には演出をかけない作りにしました。
  • 数字が0から増えていく演出。 初期値を0にしていたため、事前描画すると「0名規模」と焼き込まれました。 初期値は最終的に見せたい値にし、ブラウザで開いたときに0へ戻してから数え上げるようにしています。
  • 外部から取ってくるイベント情報。 ビルドした時点の予定を HTML に焼き込むと、数か月後も古い予定を配信し続けます。 こうした情報は事前描画には含めず、ブラウザで開いたときに取得しています。

もう一つの穴:よくある質問の「答え」が無かった

事前描画を入れたあと、よくある質問のページを調べると、本文は811文字しかありませんでした。 質問の一覧だけが並び、答えが入っていなかったのです。

このページは、質問を押すと答えが開く作りで、閉じているあいだは答えを画面から外していました。 答えは13問分すべてあるのに、HTML のどこにも出ていなかったことになります。

そこで、質問と答えを構造化データ(schema.org の FAQPage)としてページに載せました。 画面の見た目は変えずに、13問すべての答えを、機械が読める形で HTML に入れています。

見落としていたこと:構造化データが3週間壊れていた

作業の途中で、全ページ共通の構造化データ(会社の情報やロゴを示すもの)が、 JSON として読めない状態になっていることに気づきました。

原因は、2026年9月27日の修正で、不要になった項目を消したときに残ったカンマ1つです。 構造化データは JSON として読めないとブロックごと無視されますが、画面には何の変化も出ません。 そのまま3週間配信していました。

同じことを繰り返さないよう、今はビルドのたびに全ページの構造化データを読み込んで検査し、 1つでも壊れていればビルドを止めて公開しないようにしています。 わざと壊して、実際にビルドが止まることも確かめました。

直したあとの結果

同じ方法(User-Agent を GPTBot にした curl)で取得し直した結果です。

ページ直す前直した後
トップ本文 0文字本文 5,061文字
よくある質問本文 0文字本文 811文字と、13問の答え(構造化データ)
地域イベントポータル本文 0文字本文 2,510文字
題名・正規URL全ページがトップページのもの・なしページごとに正しい値

自分のサイトで確かめる方法

お使いのサイトが同じ状態かどうかは、次のコマンドで確かめられます。 返ってきた HTML の <body> の中に本文が入っていなければ、 JavaScript を実行しない相手には中身が届いていません。

curl -A "GPTBot" https://(確かめたいページのURL)

確かめるときの目安です。

  • 本文(見出しや段落の文字)が HTML に含まれているか
  • <title> がそのページの題名になっているか(トップページのままになっていないか)
  • <link rel="canonical"> がそのページの URL を指しているか
  • 構造化データ(application/ld+json)が JSON として読めるか

画面で見て問題が無くても、機械から見える姿は別物でした。 当社も、自社のサイトで実際に取得してみるまで気づけませんでした。

ブログの一覧へ

サイトや情報発信のご相談はこちらから

相談・お見積もりは無料です。

無料で相談する