SSRでOGPが正しく表示される仕組み

SSRでOGPが表示される本当の理由

SNSにURLを貼ったのに、サムネイル画像が出ない。タイトルも説明文も空白になる。この問題はサイトの品質ではなくレンダリング方式が原因であるケースが非常に多いです。

結論から言うと、OGP(Open Graph)が正しく表示されるかどうかは「HTMLがサーバから最初に返ってくる時点でメタタグが存在しているか」でほぼ決まります。SSRはこれを満たし、SPAは満たさない場合があるため、差が生まれます。

これはSEOの話ではなく、「クローラがページをどう読み取るか」という通信レベルの挙動の違いです。

OGPとは何を見ているのか

OGPはSNSの機能です。Twitter(X)やFacebook、Slack、Discordなどは、URLが投稿されるとそのURLにアクセスし、HTMLを読み取り、headタグ内のmeta情報を抽出します。

代表的なOGPメタタグは以下です。

<meta property="og:title" content="記事タイトル">
<meta property="og:description" content="記事の説明文">
<meta property="og:image" content="https://example.com/thumbnail.png">
<meta property="og:url" content="https://example.com/article">
<meta property="og:type" content="article">

ここで重要なのは「ブラウザと違い、SNSクローラはページを表示していない」という点です。
彼らはレンダリングエンジンではなく、HTMLパーサとして動いています。

クローラが実際にやっている処理

SNSクローラの挙動はシンプルです。

手順 内容
1 URLにHTTPリクエスト
2 返ってきたHTMLを受信
3 head内のmetaタグを解析
4 カード情報を生成

ここには「JavaScript実行」という工程がありません。

ここがSSRとSPAの分岐点になります。

SPAでOGPが壊れる理由

SPA(Single Page Application)は、初回アクセス時に以下のHTMLを返します。

<html>
<head>
  <title>Loading...</title>
</head>
<body>
  <div id="app"></div>
  <script src="/bundle.js"></script>
</body>
</html>

そしてJavaScriptが実行され、DOMを生成し、headを書き換えてメタタグを追加します。

ブラウザでは正しく表示されます。しかしSNSクローラは違います。

クローラは「bundle.js」を実行しません。
つまり、metaタグが生成される前のHTMLしか見ていません。

結果として、クローラが受け取る情報はこうなります。

  • og:title → 存在しない
  • og:image → 存在しない
  • og:description → 存在しない

これが「シェアカードが壊れる」原因です。

JavaScript実行問題

「最近のクローラはJSを実行する」と言われることがありますが、これは検索エンジン(Googlebot)の話です。SNSクローラは基本的に実行しません。

理由は単純で、SNSは高速性と安全性を優先しているためです。
外部サイトのJavaScriptを実行することは、パフォーマンスとセキュリティのリスクになります。

SSRが解決していること

SSR(Server Side Rendering)は、サーバがHTMLを生成してから返します。

<html>
<head>
  <title>SSRの記事タイトル</title>
  <meta property="og:title" content="SSRの記事タイトル">
  <meta property="og:description" content="SSRの記事説明">
  <meta property="og:image" content="https://example.com/ogp.png">
</head>
<body>
  <div id="app">
    <!-- すでに描画済みのHTML -->
  </div>
</body>
</html>

クローラはこの時点のHTMLを取得します。
つまり、JavaScriptを一切実行しなくてもOGP情報が存在します。

これがSSRでOGPが表示される仕組みです。

実装例(Next.js)

Next.jsではheadにメタタグを入れるだけで成立します。

import Head from 'next/head'

export default function Article({ article }) {
  return (
    <>
      <Head>
        <title>{article.title}</title>
        <meta property="og:title" content={article.title}/>
        <meta property="og:description" content={article.description}/>
        <meta property="og:image" content={article.thumbnail}/>
      </Head>
      <article>{article.body}</article>
    </>
  )
}

さらに重要なのはデータ取得方法です。

export async function getServerSideProps() {
  const article = await fetchArticle()
  return { props: { article } }
}

この時、HTML生成時点で記事データが存在します。
そのためOGPも完全な状態で出力されます。

実務で起きやすい失敗

SSRを導入してもOGPが壊れることがあります。原因は「クライアントサイドでメタを変更している」ケースです。

例えば次のような構成です。

  • 最初は共通テンプレートでSSR
  • 記事データはuseEffectで取得
  • 取得後にtitleを書き換え

これは見た目はSSRですが、OGP的にはSPAと同じ挙動になります。
クローラが取得した時点ではメタ情報が存在しないためです。

キャッシュの罠

もう一つ多いのがSNS側キャッシュです。
SNSは一度取得したOGP情報を保持します。

そのため、

  • OGPを修正した
  • しかし表示が変わらない

という現象が起きます。これはSSRの問題ではありません。

代表的な再取得ツールです。

https://cards-dev.twitter.com/validator
https://developers.facebook.com/tools/debug/

ここで再クロールさせると更新されます。

SSRが向いているサイト

特に効果が大きいのは以下です。

  • ブログ
  • ニュースサイト
  • EC商品ページ
  • LP(ランディングページ)

共通点は「URL単位で内容が意味を持つ」点です。
URL共有が前提のサイトはOGPが非常に重要です。

逆に、社内ツールや管理画面ではほぼ不要です。

注意点

SSRは万能ではありません。
リクエストごとにHTMLを生成するため、サーバ負荷が増えます。

OGP目的だけで全面SSRにすると、コストとパフォーマンスのバランスが崩れる可能性があります。
その場合は静的生成や一部ページのみSSRにする設計が現実的です。

まとめ

OGP表示の可否は、フレームワークの問題でもSEOの問題でもありません。
クローラが受け取る最初のHTMLに情報があるかどうかだけです。

SPAはブラウザのための技術、SSRはネットワークのための技術とも言えます。
SNSシェアを前提にするページでは、表示の美しさより「最初の1レスポンスに何が含まれているか」を意識すると設計を誤りにくくなります。