- SPAでSNSシェアカードが壊れるのはなぜか
- SNSクローラはブラウザではない
- SPAの初期HTMLの中身
- メタタグ書き換え問題
- GoogleとSNSの違い
- 実務でよくある誤解
- 解決策
- ありがちな落とし穴
- どのサイトが影響を受けるか
- 注意点
- まとめ
SPAでSNSシェアカードが壊れるのはなぜか
URLを貼ると、サムネイルが出ない。タイトルも説明文も空白。
ブラウザでは正しく表示されているのに、Twitter(X)やSlackでは崩れる。この現象は、フロントエンドのバグではなくSPAの仕組みそのものが原因である場合がほとんどです。
問題の核心は「SNSクローラがJavaScriptを実行しない」という点にあります。
そしてSPAは「JavaScriptを実行しないとページが存在しない」構造を持っています。
つまり、壊れているのはOGPではなく、クローラから見たページの存在です。
SNSクローラはブラウザではない
まず前提として、SNSはブラウザを使ってページを表示していません。
URLが投稿されると、SNSは独自のクローラ(bot)を使ってページを取得します。
クローラが行う処理は非常に単純です。
| 処理 | 内容 |
| 接続 | HTTPリクエストを送る |
| 取得 | HTMLを受信 |
| 解析 | headタグのmetaを読む |
| 生成 | カード情報を作る |
ここに「JavaScriptの実行」は含まれていません。
安全性と速度のため、外部サイトのスクリプトを実行しない設計になっています。
SPAの初期HTMLの中身
SPAの初回レスポンスを実際に見ると、次のような内容になっています。
<!DOCTYPE html> <html> <head> <title>My App</title> </head> <body> <div id="root"></div> <script src="/main.js"></script> </body> </html>
ここには記事も画像も説明文もありません。
アプリケーションの本体は「main.js」にあります。
ブラウザは以下の流れでページを表示します。
- HTMLを受信
- JavaScriptをダウンロード
- JavaScriptを実行
- APIからデータ取得
- DOM生成
- メタタグ書き換え
しかしクローラは、JavaScriptを実行しないため、この後半の処理が一切行われません。
結果としてクローラが認識するページは「空のdivだけのページ」になります。
メタタグ書き換え問題
多くのSPAでは、ページ表示後にタイトルやOGPを設定します。
document.title = article.title const meta = document.createElement('meta') meta.setAttribute('property', 'og:title') meta.setAttribute('content', article.title) document.head.appendChild(meta)
ブラウザでは問題ありません。
しかしクローラの時系列は次の通りです。
1 HTML受信
2 head解析
3 OGP確定
この時点ではJavaScriptは実行されていません。
つまり、後から追加されたmetaタグは存在しない扱いになります。
これが「SPAでSNSカードが壊れる原因」です。
GoogleとSNSの違い
ここで混乱が起きやすいのが「GoogleはJSを実行する」という話です。
確かにGooglebotはJavaScriptレンダリングを行います。
しかし重要なのは以下です。
| 対象 | JavaScript実行 |
| Google検索 | 実行する |
| SNSクローラ | ほぼ実行しない |
SEOが問題ないのにOGPが壊れるのは、この差によるものです。
なぜSNSはJSを実行しないのか
理由は2つあります。
- セキュリティリスク
- クロールコスト
もしSNSがすべてのページのJavaScriptを実行した場合、悪意あるコードの影響を受けます。また、表示速度も著しく低下します。
そのため「静的HTMLのみ解析」という設計が採用されています。
実務でよくある誤解
特に多いのが次の対応です。
- helmetを入れた
- titleを変更した
- React HelmetでOGPを書いた
それでも表示されません。
理由はシンプルで、Helmetは「ブラウザで実行された後」にheadを書き換える仕組みだからです。クローラには届きません。
解決策
解決方法は大きく3つあります。
1 SSR(Server Side Rendering)
サーバ側でHTMLを生成し、最初からmetaタグを含めます。
最も確実な方法です。
2 SSG(静的生成)
ビルド時にHTMLを生成しておきます。
ブログや記事サイトに向いています。
3 Prerender
クローラアクセス時のみレンダリング済みHTMLを返します。
既存SPAの応急処置として使われることがあります。
if ($http_user_agent ~* (facebookexternalhit|Twitterbot)) { proxy_pass http://prerender; }
ただし、これは運用が複雑になりがちです。
ありがちな落とし穴
API依存ページ
SSRを導入しても、記事データをクライアントで取得していると意味がありません。
「HTML生成時点」でデータが存在している必要があります。
キャッシュ問題
SNSはOGPをキャッシュします。
修正後も変わらない場合、キャッシュの可能性が高いです。
https://cards-dev.twitter.com/validator
https://developers.facebook.com/tools/debug/
ここから再取得を行う必要があります。
どのサイトが影響を受けるか
特に影響が大きいのは以下です。
- ブログ記事
- ニュース
- EC商品ページ
- 採用ページ
- LP
URL共有が前提のサイトでは、OGPはクリック率に直結します。
一方、社内システムや管理画面ではほぼ問題になりません。
注意点
SPAをやめる必要はありません。
問題なのは「公開ページまでSPAにしてしまう設計」です。
アプリケーション領域はSPA、公開ページはSSRという分離が現実的です。
ここを誤ると、リリース後にOGP問題が発覚し、構成を作り直すことになります。
まとめ
SNSシェアカードが壊れるのは、フレームワークの出来不出来ではありません。
SPAは「実行して完成するページ」、SNSクローラは「受信した瞬間のHTML」を見ています。
つまり、両者が見ているものが違います。
公開ページを作るときは、ユーザーのブラウザだけでなく、最初のHTTPレスポンスを読む存在を意識すると、設計の失敗を避けやすくなります。