この記事の要点
- 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 |
|---|---|---|---|---|
| /faq | 6,965 バイト | トップページのもの | 0文字 | なし |
| /event-portal | 6,965 バイト | トップページのもの | 0文字 | なし |
| /tottori-ai-study | 6,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 として読めるか
画面で見て問題が無くても、機械から見える姿は別物でした。 当社も、自社のサイトで実際に取得してみるまで気づけませんでした。