「モダンなWeb開発ならSPA(Single Page Application)一択」
そう信じて走り出したプロジェクトが、リリース後にSEOで苦戦する。
これは今も現場で繰り返されている、静かな事故です。
確かに Googlebot は進化しました。
しかし、
「JavaScriptを実行できる」こと
と
「HTMLと同じコストで即座に理解できる」こと
この二つは、まったく別物です。
SPA(特にCSR)が抱える構造的なSEOリスク と、
それを回避しつつモダンなUXを実現する Rails + Hotwire のアプローチを、
技術的エビデンス付きで解説します。
🕷️ Googlebot と SPA の現実
Two-wave Indexing(2段階インデックス)
Googleのクロール処理には Two-wave Indexing(2段階インデックス) という仕組みがあります。
これが、SPAのSEOを難しくしている根本原因です。

⏱️ なぜ「遅延」が発生するのか
🟢 第1波(Initial Crawl)
- HTTPリクエスト
- 返却された HTMLのみを解析
- JavaScriptは実行されない
🟡 待機(Rendering Queue)
- JS実行はCPUコストが高い
- ページはレンダリングキューに積まれる
🔵 第2波(Rendering)
- ヘッドレスブラウザでJSを実行
- 生成されたDOMを解析してインデックス
問題はこの 待機時間 です。
サイト規模やクロールバジェット次第では、
数時間〜数日 のタイムラグが発生します。
ニュース、EC、キャンペーンページでは、
この遅延は静かに、しかし確実に致命傷になります。
🔍 証拠:Googlebotが見ている世界
❌ 典型的なReact CSR実装
// ❌ SEOに弱い典型的なCSR実装
import { useState, useEffect } from "react";
export default function NewProductPage() {
const [product, setProduct] = useState(null);
useEffect(() => {
fetch("/api/products/latest")
.then(res => res.json())
.then(data => {
setProduct(data);
document.title = data.name;
});
}, []);
if (!product) return <div>Loading...</div>;
return (
<article>
<h1>{product.name}</h1>
<p>{product.description}</p>
</article>
);
}
第1波のGooglebot視点(curl)
<!DOCTYPE html>
<html>
<head>
<title>React App</title>
</head>
<body>
<div id="root">Loading...</div>
<script src="/static/js/main.js"></script>
</body>
</html>
結論
第1波の時点で、
このページは 「Loading...」としか書かれていない
コンテンツとして認識されます。

🥪 「サンドイッチ構造」の代償
この問題を解決するため、
Next.js(SSR) + Rails API
という BFF / サンドイッチ構造 がよく採用されます。
SEOは確かに改善します。
しかし、引き換えに失うものは 開発の単純さ です。
🧨 失われるRailsの生産性
- 🔁 二重管理
- ルーティング
- バリデーション
- 型定義
- 🔐 認証の複雑化
- JWT
- Cookie戦略
- CORS設計
- 🧯 インフラコスト増
- Rails + Node.js の二重運用
「SEOのため」の選択が、
速度ではなく、摩擦を増やす 結果になることも珍しくありません。

🌱 Rails Hotwireという「第三の選択肢」
Hotwireの思想は極めて明快です。
JSONではなく、完成されたHTMLを送る
(HTML Over The Wire)
これにより、次の二面性を同時に満たします。
- 🤖 Botに対して
- 伝統的なHTMLサイト
- 即時インデックス
- 🧑 人に対して
- SPAのような高速遷移
- スムーズなUX
🧩 コードで見る Hotwire の解決策
🧠 Controller(いつものRails)
# app/controllers/products_controller.rb
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
end
end
🧾 View(完成されたHTML)
<article id="product_content">
<h1><%= @product.name %></h1>
<p><%= @product.description %></p>
</article>
$ curl https://example.com/products/1
<!DOCTYPE html>
<html>
<head>
<title>すごい新商品 | My Shop</title>
</head>
<body>
<article id="product_content">
<h1>すごい新商品</h1>
<p>これは革命的な商品です...</p>
</article>
</body>
</html>
Two-wave indexing の待ち時間はゼロ。
Googlebotは、アクセスした瞬間にすべてを理解します。
⚡ なぜSPAのように速いのか?
🚀 Turbo Drive の仕組み
Turbo Drive が裏で静かに働きます。
- リンククリック
- Turboが通常遷移をキャンセル
- fetchで次ページHTML取得
<body>を丸ごと差し替え
JSやCSSは再読み込みされません。
結果として、
- 見た目はSPA
- 実態はHTML遷移
この「正体」が、Hotwireの強さです 🌱
🧭 結論:技術選定の羅針盤
SPAは悪ではありません。
- Trello
- 高度な操作性が主役のアプリ
これらではSPAが最適解です。
しかし、もしあなたのプロジェクトが:
- 🔎 SEOが生命線
- 🔄 更新頻度が高い
- 👥 少人数チーム
- 🚄 Railsの生産性を活かしたい

ならば、
Rails + Hotwire は妥協案ではなく、
最も合理的で、持続可能な最適解
です。
「何を足すか」ではなく、
「何を増やさないか」。
その視点こそが、
スケールする設計への近道になります 🚦
