01 / 本文
結論:HTMLソースを見たら、リンクが0件だった
運営しているサイトのトップページに、SEO対策をしようとした。まず現状を測ろうと思って、curl で取得してリンクを数えた。
0件だった。
Vite + React で作っていて、<div id="root"></div> が空のまま配信されていた。見出しも、説明文も、ナビゲーションも、すべてJavaScriptが後から描いていた。
ブラウザで開けば普通に表示される。だから気づかなかった。
curl -sSL https://example.com/ | grep -c 'href="/'
# → 0
curl -sSL https://example.com/ | grep -o '<div id="root">.*</div>'
# → <div id="root"></div>02 / 本文
Googleは実行する。生成AIのクローラーは実行しないことが多い
Googlebot は JavaScript を実行する。だから SPA でも検索には載る(載るまでに時間はかかる)。
問題は、それ以外のクローラーだ。生成AIが参照するために巡回するクローラーの多くは、HTMLを取得するだけでJavaScriptを実行しない。
つまり、AIから見た私のトップページは真っ白なページだった。サイトの説明も、どこに何があるかも、何も伝わっていなかった。
検索経由の流入だけを考えていれば問題は表面化しない。しかし生成AIに引用される経路を考えると、これは致命的だった。
03 / 本文
直し方:#root の中に、静的な中身を書いておく
サーバーサイドレンダリングに移行するのが本筋だが、静的サイトとして配信している都合上、そこまでは変えたくなかった。
採った方法は単純だ。<div id="root"> の中に、要点と主要な導線を静的に書いておく。
React の createRoot はマウント時に中身を置き換えるので、JavaScript が動けば消える。見た目は何も変わらない。
JavaScript を実行しないクローラーには、この静的な中身が見える。
<body>
<!-- React が読み込まれるまでの中身。JSを実行しないクローラーが
見るのはここなので、要点と主要な導線を静的に置いておく。
描画が始まると React がまるごと置き換える。 -->
<div id="root">
<main>
<header>
<p>サイト名</p>
<nav>
<a href="/index-a/">索引A</a>
<a href="/index-b/">索引B</a>
</nav>
</header>
<section>
<h1>このサイトが何をするか</h1>
<p>どんなデータを、どこから取って、何を載せているか。
載せていないものは何か。</p>
</section>
</main>
</div>
<script type="module" src="/src/main.jsx"></script>
</body>04 / 本文
あわせて入れたもの
静的な中身を置いたついでに、AI向けの手当てを3つ入れた。
1. 構造化データを厚くした。 WebSite だけだったところに Dataset を足し、何を収録しているか、どこから取ったか、独自の評価を含まないことを機械可読な形で書いた。一覧ページには CollectionPage と ItemList、詳細ページには BreadcrumbList を入れた。
2. llms.txt を置いた。 サイトの要約、載せているもの、載せていないもの、数え方の制約を書いたテキストファイルをルートに置く。標準化の途上だが、置くコストは低い。
3. robots.txt でAIのクローラーを明示的に許可した。 GPTBot、ClaudeBot、PerplexityBot、Google-Extended などを列挙した。載せているのは公開情報だけなので、隠す理由がない。
# public/llms.txt の骨組み
# サイト名(example.com)
> 1〜2文で、何をするサイトかを書く。
## このサイトが載せているもの
- 具体的な項目を箇条書きで
## 載せていないもの
- 推測や独自の評価は含まない、など
## 数え方の制約
- 上限やサンプリングがあるなら、正直に書く
## 主なページ
- URLと、それが何かの説明
## 出典
- 一次情報のURL05 / 本文
llms.txt に「載せていないもの」を書く
llms.txt で一番効くと感じたのは、載せていないものと、数え方の制約を書く部分だ。
私のサイトでは、集計に上限がある。「実際より少なく出ることがある」と明記した。AIが数字を引用するとき、この但し書きまで伝わってほしい。
「推測や独自の評価は含まない」と書くのも同じ理由だ。どこまでが事実で、どこからが解釈でないかを先に宣言しておく。
これは SEO というより、誤って引用されないための予防だと思っている。
06 / 本文
確認のしかた
対策したあと、JavaScript を実行しない状態で何が見えるかを確かめる。curl で取って数えるだけでいい。
# 主要な導線がHTMLに入っているか
curl -sSL https://example.com/ | grep -c 'href="/'
# 構造化データが出力されているか
curl -sSL https://example.com/ | grep -c 'application/ld+json'
# llms.txt が配信されているか
curl -sS -o /dev/null -w '%{http_code}
' https://example.com/llms.txt07 / 本文
まとめ
SPA のHTMLソースは空になる。 ブラウザで見えているものと、クローラーが受け取るものは違う。
Googlebot は JavaScript を実行するが、生成AIのクローラーの多くは実行しない。AIからの参照を考えるなら、HTMLに中身が要る。
サーバーサイドレンダリングに移せないなら、#root の中に静的な中身を置くだけでも効く。React が置き換えるので、見た目は変わらない。
llms.txt には、載せているものだけでなく 載せていないものと数え方の制約を書く。誤った引用を防ぐ側に効く。
そして、対策したら curl で取って数える。ブラウザで確認しても意味がない。
コメント
この記事へのコメント
お名前だけで投稿できます。アカウント登録やログインは不要です。 いただいたコメントは確認のうえ公開します。