【Next.js】OGPが出ない原因は「ローディング画面」だった?SEOを殺す実装ミスと解決策

【Next.js】OGPが出ない原因は「ローディング画面」だった?SEOを殺す実装ミスと解決策 のビジュアル

Next.jsで構築したサイトで「ブラウザでは見えるのにOGPが出ない」「Googleにインデックスされない」というトラブルに遭遇していませんか?
その原因、実は「ローディング画面の実装方法」にあるかもしれません。
本記事では、SSR(サーバーサイドレンダリング)時にクローラーへ「空のHTML」を返してしまっていた失敗談と、curlコマンドを使った調査方法、そして正しい実装パターンをコード付きで解説します。

  • 見えざるSEO事故ブラウザで正常でも、SSRの記述ミスによりクローラーにコンテンツが渡っていないケースがある
  • 調査の鍵はcurlブラウザの検証ツールではなく、curlコマンドでボット視点の「生のHTML」を確認することが重要
  • 条件付きレンダリングの罠認証チェックなどで安易に return を分けると、サーバー側でコンテンツ描画がスキップされる
  • 解決策はオーバーレイコンテンツを常にレンダリングしつつ、ローディング画面をCSSで前面に「重ねる」実装が正解

はじめに:完璧に動くサイトなのにOGPが出ない?

Webサイト開発の現場において、最も恐ろしいバグの一つ。それは「ブラウザ上では完璧に動いているのに、裏側で致命的な問題が起きている」バグです。

今回は、私がNext.jsの案件で実際に遭遇した、「ローディング画面の実装ミスにより、SEOが全滅しかけた話」を共有します。

もしあなたが、「Next.jsでサイトを作ったのにOGPが反映されない」「Google検索結果にファビコンが出ない」といった症状に悩まされているなら、この記事が解決の糸口になるはずです。

事件の発生:ステータスコード200でも「認識されない」

サイトの構築が完了し、本番環境へデプロイ。ブラウザで動作確認を行いました。 アニメーションも滑らか、ページ遷移も爆速。「完璧だ」と思いました。

しかし、SNSでシェアしようとした瞬間、異変に気づきます。

  • Twitter(X)やFacebookでURLを貼っても、OGP画像が表示されない。
  • Googleの検索結果に、いつまで経ってもファビコンやディスクリプションが反映されない。

FacebookのデバッガーツールでURLを叩いてみると、HTTPステータスコードは 200 OK を返しています。サーバーは生きている。しかし、タイトルや画像情報は「取得できませんでした」となるのです。

迷走したデバッグ:認証やキャッシュを疑う日々

最初は、Next.jsやVercelの設定周りを疑いました。

  • 「VercelのAuthentication(Basic認証など)が誤ってONになっている?」→ 確認したがOFF。
  • 「CSP(Content Security Policy)ヘッダーが厳しすぎてクローラーを弾いている?」→ 無効化してみたが変わらず。
  • 「ボットのキャッシュが残っているだけ?」→ URLパラメータを変えてアクセスしても状況は変わらず。

設定ファイルを見直しても、怪しい箇所は見当たりません。完全に迷宮入りしかけていました。

決定的な証拠:curlコマンドが見た「虚無」の世界

「ブラウザで見えているものが、なぜクローラーには見えないのか?」

原点に立ち返り、「ボットの視点」でサイトを見てみることにしました。ここで役立ったのが、ブラウザの検証ツールではなく、ターミナルで実行する curl コマンドです。

以下のコマンドで、Facebookのクローラー(facebookexternalhit)を偽装してアクセスしてみました。

cli
curl -A "facebookexternalhit/1.1" [<https://www.tosakafunk.com>](<https://www.tosakafunk.com>)

返ってきたHTMLを見て、私は愕然としました。

html
<html>
  <head>
    </head>
  <body>
    <div id="__next">Loading...</div>
  </body>
</html>

中身がスカスカだったのです。<head> 内のタイトルもOGPタグも存在せず、ボディには「Loading...」の文字だけ。これでは、クローラーがサイトの内容を理解できるはずがありません。

ブラウザの検証ツールだけでは気づけない罠

普段私たちは、Chromeなどの「検証ツール(DevTools)」でHTMLを確認しがちです。しかし、最近のブラウザは賢いため、JavaScriptを実行した「後」のDOMを表示します。

対して、多くのクローラー(特にOGP取得ボット)は、サーバーから返された最初のHTML(初期レスポンス)を見て判断します。ここに致命的な乖離があったのです。

真犯人:SSR時の条件付きレンダリング

原因は、アプリケーションのルートとなる _app.tsx での条件付きレンダリング(Conditional Rendering)の書き方にありました。

認証状態などをチェックする際、ユーザー体験を良くしようとして「チェック中はローディング画面だけを見せる」という実装をしていたのです。

❌ 犯人のコード

_app.tsxtypescript
// _app.tsx(SEOを殺していたコード)

function MyApp({ Component, pageProps }) {
  const { isChecking } = useAuth(); // 認証チェックフック

  // ⚠️ ここが問題!
  // チェック中はPreloaderだけを返し、Component(本来のコンテンツ)を描画していない
  if (isChecking) {
    return <Preloader />;
  }

  return <Component {...pageProps} />;
}

なぜこれがダメなのか?

  1. useAuthuseEffect などのフックは、クライアントサイド(ブラウザ)でのみ実行されます。
  2. Next.jsのSSR(サーバーサイドレンダリング)の時点では、isChecking は初期値(例えば true)のまま処理が進みます。
  3. サーバーは if (true) に従い、<Preloader /> だけが含まれたHTMLを生成してレスポンスを返します。
  4. クローラーはその「PreloaderだけのHTML」を受け取り、「中身のないページだ」と判断して帰っていきます。

人間が見るブラウザ上では、あとからJSが動いてコンテンツに切り替わるため気づきませんが、ボットにとっては「永久にローディング中のページ」だったのです。

解決策:「切り替え」ではなく「重ね合わせ」る

解決策は単純でした。 「読み込み中かどうかにかかわらず、常にコンテンツ(Component)を描画しておく」ことです。その上で、ローディング画面をCSSで上から被せる(オーバーレイ)実装に変更しました。

⭕️ 正しいコード

_app.tsxtypescript
// _app.tsx(SEOに優しいコード)

function MyApp({ Component, pageProps }) {
  const { isChecking } = useAuth();

  return (
    <>
      {/* Preloaderは条件付きで表示するが、
        メインコンテンツとは並列(または前面)に配置する
      */}
      {isChecking && <Preloader />}
      
      {/* ⚠️ 重要:
        チェック中であっても、裏側で常にComponentを描画出力しておく!
        これでSSR時にもHTMLの中にコンテンツが含まれるようになる。
      */}
      <Component {...pageProps} />
    </>
  );
}

CSSでの制御(Preloader)

Preloader コンポーネントは position: fixed; z-index: 9999; などで画面全体を覆うようにスタイリングします。これで、見た目上の挙動(ローディング中はコンテンツが見えない)は維持しつつ、HTML構造上はコンテンツが存在する状態を作れます。

この修正をデプロイした直後、curl コマンドで確認すると、しっかりと <title><meta property="og:image"> が出力されるようになり、SNSでの表示も無事に復活しました。

まとめ:Reactの感覚でSSRを扱うとSEOで死ぬ

SPA(シングルページアプリケーション)やReactだけの開発に慣れていると、「条件によって return を分ける」という書き方を頻繁に行います。しかし、Next.jsのようなSSRを伴うフレームワークでは、「サーバーが返す初期HTMLに何が含まれているか」を常に意識しなければなりません。

今回の教訓:

  1. 条件付きレンダリングには要注意: SSR時にコンテンツが除外されないか確認する。
  2. curlコマンドは友達: SEO系のトラブルシューティングでは、ブラウザではなく curl で生のHTMLを確認する癖をつける。

「なぜかOGPが出ない」と悩んでいる方は、ぜひ一度ターミナルから自分のサイトにアクセスしてみてください。意外な「虚無」が見つかるかもしれません。

TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発 [ 手島 拓也 ]

TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発 [ 手島 拓也 ]

参考リンク

この記事は役に立ちましたか?