概要
Next.jsで構築したWebサイトにおいて、OGP画像がSNSで正しく表示されない問題が発生しました。
調査の結果、SSR(サーバーサイドレンダリング)時にクライアントオブジェクトである document
を参照していたことが原因であると判明しました。
本記事ではその原因と対応策をまとめます。
問題の発生状況
- Next.jsで構築したサイトをFacebookやX(旧Twitter)でシェア
- OGP画像が表示されない
-
meta
タグは正しく出力されている - OGP画像URLにも直接アクセス可能
調査の流れ
-
metaタグの確認
以下のように正しく記述されていたため、タグのミスではないと判断。<meta property="og:image" content="https://example.com/ogp.jpg" /> <meta property="og:title" content="サイトタイトル" />
-
Facebook Sharing Debuggerで確認
Facebook Sharing Debugger にてURLを入力したところ、
500 Internal Server Error が返されていました。 -
Cloud Runのログ確認
GCP(Google Cloud Run)のログを確認しましたが、payloadNotSet
と表示されており、スタックトレースなどの詳細情報は取得できませんでした。 -
関連情報の調査
以下の記事を発見し、類似の問題であると推測しました。
https://blog.toru-takagi.dev/article/44/
原因
サーバーサイドレンダリング中に document.cookie
を参照していたことが原因でした。
以下のようなコードが存在しており、これがSSR中にも実行されてしまい、
ReferenceError: document is not defined
により500エラーが発生していました。
const hasCookie = document.cookie.includes('animationPlayed=true')
このコードは "use client"
を記述しているファイル内で使用されていたため、
サーバーでは実行されないと思い込んでいました。
しかし、Next.jsにおけるクライアントコンポーネントであっても、SSR時には関数本体が一度だけ評価されるため、
document
などのクライアント専用オブジェクトを直接参照するとクラッシュします。
対応方法
クライアント側でのみ document
を参照するよう、useEffect
内に処理を移動することで対応しました。
修正前(NG)
const hasCookie = document.cookie.includes('animationPlayed=true')
修正後(OK)
const [hasCookie, setHasCookie] = useState(false)
useEffect(() => {
setHasCookie(document.cookie.includes('animationPlayed=true'))
}, [])
useEffect
はクライアント側でのみ実行されるため、SSR中に document
を参照することがなくなり、500エラーも解消されました。
なぜ「use client」だけでは不十分なのか
"use client"
を付けたコンポーネントはクライアントコンポーネントとして扱われますが、
Reactが最初にHTMLを生成するためにコンポーネント関数本体を一度実行するという挙動は変わりません。
そのため、以下のような処理はSSR中にも評価されるため、注意が必要です。
// SSR中にも実行される(NG)
const width = window.innerWidth
const cookie = document.cookie
このような処理は、必ず useEffect
または typeof window !== 'undefined'
等でガードする必要があります。
OGPとSSRの関係について補足
OGPは、ページのHTMLに含まれる <meta>
タグの情報をもとにSNS側で展開されます。
FacebookなどのクローラーはJavaScriptを実行せず、SSRによって返されたHTMLのみを解析します。
つまり、SSR中にクラッシュしてHTMLが正しく返されない場合、OGP情報も取得できず、画像やタイトルが一切表示されないという結果になります。
まとめ
- OGP画像が表示されない場合は、Facebook Debuggerなどでステータスコード(500など)を確認する
-
"use client"
を記述していても、関数本体の評価はSSR中に一度だけ実行される点に注意 -
document
,window
,navigator
,localStorage
などのクライアント専用オブジェクトは、useEffect
内でのみ使用する
最後に
通常のブラウザの開発者ツールでは特に問題ないように見えるので解決に時間がかかりました
同様の現象に悩まれている方の参考になれば幸いです。