Reactスターターキットの続きです。前回はfavicon画像の作成、推奨サイズ、スマホ向け設定などを行いました。今回はSNSにシェアした時に表示されるOGPを設定します。helmetを使ってルーティングのページごとにOGPを設定しましたが、SPAでは動的なOGPはうまくいかず。。静的なOGPなら設定できました。それではいきましょう!
【目次】
- 前回のおさらい
- OGPとは
- OGP画像の作成
- HelmetでOGP設定
- 動的OGPの挙動確認(失敗)
- SPAでは動的OGPできない。。
- index.htmlに静的OGPを直書き
- 静的OGPの挙動確認(成功!)
- 下層ページもMainページ扱いにしたいが難しそう
- メモ:動的OGPを解決する方法
- 最後に
※参考:前回記事
【React】favicon画像の作成、推奨サイズ、スマホ向け設定など(Reactスターターキット) - クモのようにコツコツと
※参考:【React】ReactでWebアプリを作るシリーズまとめ
qiita.com
前回のおさらい
favicon画像の設定。推奨サイズを調べて四種類の画像を作成。favicon作成ジェネレーターを利用してマルチアイコン作成、スマホ向け設定も。
※参考:【React】favicon画像の作成、推奨サイズ、スマホ向け設定など(Reactスターターキット) - クモのようにコツコツと
今回はSNSでシェアされた時に表示されるOGPを設定する。
OGPとは
OGPとは、「Open Graph Protcol」の略でFacebookやTwitterなどのSNSでシェアした際に、設定したWEBページのタイトルやイメージ画像、詳細などを正しく伝えるためのHTML要素です。
※参考:OGPを設定しよう!SNSでシェアされやすい設定方法とは?
OGPが設定されていないページをTwitterでシェアするとこのように青色のURLしか表示されない。
一方、OGPが設定されているページをシェアするとこのようにTwitterカードが表示される(この顔アイコンのドアップもどうかと思うがw)。
自分の作ったReactアプリでもカードが表示されるようにOGPを設定したい!
OGP画像の作成
まずカードに表示されるOGP画像の推奨サイズを調べる。
Twitter と Facebook の両方に対応させるには 600 x 315 以上(横幅が 600 )
高解像度端末に対応させたい場合は倍の 1200 x 630 以上
※参考:OGP image 画像のサイズ仕様( 2020 年時点) | gotohayato.com
このページのジェネレーターに大きめの画像をアップするとOGP画像のプレビュー確認と画像作成ができる!
※参考:OGP画像シミュレータ | og:image Simulator
やってみた。いつもSNSアカウントの背景画像などに使っている画像 元画像は正方形サイズだとクモがうまく入らなかったため、Figmaで縦横比や左右位置を調整した新画像を作った。
この画像(ogp.png)を「public」フォルダの直下に追加する。
HelmetでOGP設定
React-routerでルーティング設定しているページごとにOGPを設定したい。titleタグやdescriptionと同様にHelmetで設定してみる。
metaタグとして書く方法
※参考:OGP対応(react-helmet利用) - 奇をてらったテクノロジー
Helmetの属性として書く方法
※参考:React Helmetを使ってOGP対応した - akameco Blog
両方試したが、metaタグは見た目直感的だが、Helmetの属性として書く方法がシンプルに感じた。
「data.json」に繰り返し使うデータ(ドメイン、OGP画像、Twitterアカウント)を設定。
{ "data": { "info": { "domain": "https://ryo-i.github.io", "ogpImg": "/ogp.png", "tw": "idr_zz" }, // 後略
今回、OGPの画像は1枚で統一する予定(タイトル文字を合成させた画像を作ってるサイトとか見受けられるけどおそらくサーバ側での処理がありそうな気がするので今回はスコープ外とする)
Mainコンポーネント(Main.tsx )で先ほどのデータを取り込む
const data = Data.data; // 中略 const domain = data.info.domain; const ogpImg = data.info.ogpImg; const tw = data.info.tw;
MainページのOGPを設定
<Route exact path={ homeUrl + "/" }> <Helmet title={ mainTitle } meta={[ { name: 'description', content: mainText }, { property: 'og:title', content: mainTitle }, { property: 'og:type', content: 'website' }, { property: 'og:url', content: domain + homeUrl + '/' }, { property: 'og:image', content: domain + homeUrl + '/ogp.png' }, { property: 'og:description', content: mainText }, { name: 'twitter:card', content: 'summary_large_image' }, { name: 'twitter:image', content: domain + homeUrl + '/ogp.png'}, { name: 'twitter:site', content: tw }, ]} /> <h1>{ mainTitle }</h1> <p dangerouslySetInnerHTML={{ __html: mainText }}></p> <Inner /> </Route>
ogpの基本設定
property
属性をog:title
,content
属性をmainTitle
にproperty
属性をog:type
,content
属性をwebsite
にproperty
属性をog:url
,content
属性をdomain + homeUrl + '/'
にproperty
属性をog:image
,content
属性をdomain + homeUrl + '/ogp.png'
にproperty
属性をog:description
,content
属性をmainText
に
OGPのimageのURLは絶対パスにする必要がある。
※参考:OGPとは?設定するべき理由と設定方法について | デジマギルド
Twitterカード固有の設定
name
属性をtwitter:card
,content
属性をsummary_large_image
にname
属性をtwitter:image
,content
属性をdomain + homeUrl + '/ogp.png
にname
属性をtwitter:site
,content
属性をtw
に
Twitterカードの属性の詳細
※参考:カードの利用開始 | Docs | Twitter Developer
続いてAboutページのOGPを設定
<Route path={ homeUrl + "/about" }> <Helmet title={ aboutTitle } meta={[ { name: 'description', content: aboutText }, { property: 'og:title', content: aboutTitle }, { property: 'og:type', content: 'article' }, { property: 'og:url', content: domain + homeUrl + '/about' }, { property: 'og:image', content: domain + homeUrl + '/ogp.png' }, { property: 'og:description', content: aboutText }, { name: 'twitter:card', content: 'summary_large_image' }, { name: 'twitter:image', content: domain + homeUrl + '/ogp.png'}, { name: 'twitter:site', content: tw }, ]} /> <h1>{ aboutTitle }</h1> <p dangerouslySetInnerHTML={{ __html: aboutText }}></p> <About /> </Route>
先ほどと変えたところ
property
属性をog:title
,content
属性をaboutTitle
にproperty
属性をog:type
,content
属性をarticle
にproperty
属性をog:url
,content
属性をdomain + homeUrl + '/about'
にproperty
属性をog:description
,content
属性をaboutText
に
title
やdescription
をアバウトページの内容に。あと、og:type
は下層ページはarticle
になる。
ここまでのコミット
※参考:GitHub - ryo-i/react-app-started at 79750ca98a83012ef9370a019d24e0d706e4d5bc
動的OGPの挙動確認(失敗)
ブラウザのDev-tooles、SourcesパネルにはOGP設定はないが
ElementsパネルにはOGP設定がレンダリングされている
これでうまくいくはず!と期待していた。
TwitterのOGP表示はこちらで確認できる。
※参考:Login on Twitter
--
まずMainページのURL(https://ryo-i.github.io/react-app-started/
)を入れてみる。
むむっ?
INFO: Page fetched successfully
INFO: 5 metatags were found
ERROR: No card found (Card error)
5つのメタタグが見つかりました、カードが見つかりません、と。 メタタグ自体は認識されているということか。画像を読む部分がエラー?
ちなみにAboutページ(https://ryo-i.github.io/react-app-started/about
)はもっと認識されない。。
INFO: Page fetched successfully
WARN: No metatags found
メタタグ自体がありません、と。。
SPAでは動的OGPできない。。
調べるとSPA単体では動的OGPが設定できないっぽい。
www.slideshare.net
※参考:SPA時代のOGPとの戦い方
GooglebotのクローラーはJSのレンダリングを認識してくれるが、Twitter、Facebookのクローラーは認識してくれないと。。
実際、以前作った「ジャンプ率ジェネレーター」をGoogleで検索するとHelmetで設定したtitle(ブラウザ側でJSでレンダリングされている)がちゃんとヒットしている。
※参考:React App
HelmetはGoogleクローラーはちゃんと読んでくれるんだな。Twitter、Facebookも読んで欲しいものだ。。
index.htmlに静的OGPを直書き
動的OGPは簡単にでできなそうなので、ひとまずindex.htmlに静的OGPを直に書いてみる。
「data.json」に設定したOGP情報は削除
{ "data": { /* "info": { "domain": "https://ryo-i.github.io", "ogpImg": "/ogp.png", "tw": "idr_zz" }, */
Mainコンポーネント(Main.tsx)のOGPの変数も削除
// const domain = data.info.domain; // const ogpImg = data.info.ogpImg; // const tw = data.info.tw;
MainページのmetaタグからOGP設定を削除
<Helmet title={ mainTitle } meta={[ { name: 'description', content: mainText } ]} />
AboutページもOGP設定を削除
<Helmet title={ aboutTitle } meta={[ { name: 'description', content: aboutText } ]} />
ここまでのコミット
※参考:GitHub - ryo-i/react-app-started at a6a8b0d0e07985ea8870cee0d06ee4f6772a1190
「public/index.html」にOGPを直書き
<title>Reactアプリスターターキット</title> <meta name="description" content="React + TypeScript + CSS in JS環境"> <meta property="og:title" content="Reactアプリスターターキット"> <meta property="og:description" content="React + TypeScript + CSS in JS環境"> <meta property="og:url" content="https://ryo-i.github.io/react-app-started/"> <meta property="og:image" content="https://ryo-i.github.io/react-app-started/ogp.png"> <meta property="og:type" content="website"> <meta name="twitter:site" content="@idr_zz"> <meta name="twitter:image" content="https://ryo-i.github.io/react-app-started/ogp.png"> <meta name="twitter:card" content="summary_large_image">
また、これを機にtitle
やdescription
も直書きにした。
静的OGPの挙動確認(成功!)
TwitterのOGP確認、リベンジ!
※参考:Login on Twitter
MainページのURL(https://ryo-i.github.io/react-app-started/
)を入れると…
おお、今度は表示された!
INFO: Page fetched successfully
INFO: 16 metatags were found
INFO: twitter:card = summary_large_image tag found
INFO: Card loaded successfully
16個のメタタグが見つかりました、Twitterカードの画像も見つかりました、と。
Aboutページ(https://ryo-i.github.io/react-app-started/about
)を入れると…
あー、やはりこっちはダメかー。
MainページをTwitterに実際に投稿、OGPが表示された!
下層ページは、やはりダメかー
下層ページもMainページ扱いにしたいが難しそう
下層ページのURLでシェアされてもMainページのOGPが表示されちゃって構わないんだがなー。
思うに、og:url
のURLと実際のURLが一致していないのが問題な気がする。
<meta property="og:url" content="https://ryo-i.github.io/react-app-started/">
Aboutページはこの後に/about
が続くので、それが存在しないページという認識になっているのではないかな(ちなみにindex.html
とかパラメタ?aaa=bbb
などを繋げても問題はなかった)。
canonical(カノニカル)設定で全てのURLをMainページに統一化も試してみた
canonicalを設定すると、複数の重複ページが存在している場合に、検索エンジンに優先させるべきページを伝えることでそれ以外のページは重複ページであることを伝える事ができます。
※参考:canonical(カノニカル)とは?URLの正規化でSEOのマイナス評価を避けよう|ferret
しかしうまくいかなかった。そういう問題ではないんだな。
自作のSNSシェアボタンで設定できる内容も調べたが、コアな情報は結局OGPで設定する必要があるようだった。
※参考:オリジナルのシェアボタンを作ろう!各種SNSのボタン設置用URLまとめ | Web Design Trends
メモ:動的OGPを解決する方法
動的OGPの解決するにはOGP部分だけサーバ側で設定する必要がありそう。先ほどのスライドでも後半でAWS Lambda@Edgeを使って解決していた。
www.slideshare.net
OGP部分をExpressで設定する事例。
※参考:Reactで作ったページにTwitterCardsとOGPのメタデータを埋める - sambaiz-net
OGP部分だけKV型のDBと連携する方法もあるようだ。
※参考:SPAでも動的OGP対応したい! with Cloudflare Workers, KV
あとはNext.jsで設定する方法。 speakerdeck.com
複数ページになるならSPAでルーティングせずに最初からNext.jsでマルチページアプリケーション(MPA)として作るのがいいのかもなー、と今回も改めて考えさせられた。
最後に
ということで、OGP設定、紆余曲折の結果、今回はMainページの静的OGPのみ設定しました。
ブログやECサイトなど下層ページに固有な重要な情報がある場合はNext.jsが良さそうですが、ちょっとしたSPAアプリは下層ページはたいして重要ではないので、下層ページのOGPはいったん諦めることにします。
Next.jsはSSRではサーバ側をNode環境にする必要がありますが、今はSSG(静的サイト生成)という選択肢が増えてきたので、今後、MPA構成にする場合はNext.jsにもトライしていきたく思います。
※参考:Next.jsにおけるSSG(静的サイト生成)とISRについて(自分の)限界まで丁寧に説明する - Qiita
※参考:Next.js 4年目の知見:SSRはもう古い、VercelにAPIサーバを置くな - Qiita
それではまた!
※参考:【React】ReactでWebアプリを作るシリーズまとめ
qiita.com