Firebase Hosting x ReactでOGP対応する

概要

Firebase HostingにデプロイされたReactアプリにて、
Twitterでシェアされた時にページ毎に違うOGPを設定する方法について、備忘録がてら述べます。

経緯

React Helmetを使って動的にOGPを設定しても、TwitterのクローラがJSを実行しないようなので、
ReactプロジェクトでOGPをTwitterで表示する方法として
主に、

  1. ページ毎にHTMLを事前に生成しておく
  2. アクセス時に動的にHTMLを生成する

という方法があります。

今回のプロジェクトの都合上、2の方法を選択したのでそのやり方を説明します。

もちろん、お金を使える場合はNetlifyのPrerendering機能を利用したり、
GatsbyJSなどを利用したりしている場合は、1の方法が採用できますね。
Prerendering | Netlify Docs

動的にHTMLを変更する

アクセス時に動的にHTMLを生成する方法としても、
例えばRailsがHTMLを返すシステムの場合、Rails側でリクエストに応じてHTMLを返却すれば問題なさそうです。

ここではFirebase Hostingを使っているケースで、OGPを設定する方法について述べます。

firebase.json の設定

まず、プロジェクトのfirebase.jsonで以下のように設定します。
以下の場合/posts/**にアクセスすると、Firebase FunctionsにデプロイされたaddOgTagsInPost関数に処理が移譲されます。
その他のページにアクセスすると/index.htmlが表示される設定になっています。
ここでsourcefunctionはよしなに名前を設定してください。

{
  "hosting": {
    ...
    "rewrites": [
      {
        "source": "/posts/**",
        "function": "addOgTagsInPost"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  ...
}

関数の実装

上記の例では/posts/**にアクセスされると、以下の関数が呼ばれるので、
ここでいい感じにHTMLを返してやればいいわけです。

クローラ以外の場合、正規ページにリダイレクトする方法もあるのですが、
OGPが表示されるページと、されないページが存在するのを避けたかったので、
今回は、Firebase Functions側にもHTMLファイルをデプロイしておく方法を採用しました。

以下は例なので、よしなに修正してください。
基本的にはHTMLファイルを読み込んで、</head>タグの直前にmetaタグを挿入しています。
また、ページに応じて画像のURLを変更しています。

import * as functions from "firebase-functions"
import * as fs from "fs"
import * as path from "path"

export const addOgTagsInPost = functions.https.onRequest((req, res) => {
  const uid = req.path.split("/")[2]
  const ogTags =
    '<meta name="twitter:card" content="summary_large_image"></meta>' +
    '<meta property="og:title" content="{title}" />' +
    '<meta property="og:description" content="{description}" />' +
    `<meta property="og:image" content="https://www.example.com/user_icons/${uid}.png" />`
  const htmlFile = fs.readFileSync(path.join(__dirname, "./index.html"))
  const html = htmlFile.toString().replace(`</head>`, `${ogTags}</head>`)
  res.send(html)
})

HTMLファイルのデプロイ

デプロイ前に、デプロイするjsファイルと同じディレクトリ直下に、
フロントエンドで用いているHTMLファイルを設定してください。

package.jsonscriptsに処理を、設定するといいと思います。

問題点

上記のやり方には、以下のような問題点があるので注意してください。

  • req.path.split("/")[2] のように /posts/****の部分の取り方が適当
  • HTMLが変更された場合に、当該関数の再デプロイが必要
    • 忘れがち
    • フロントエンドのコードと同じプロジェクトではない場合、どうする?
  • Firebase Functionsを挟むので、ロード時間が比較的長くなる
    • React-Routerで遷移したなどは、もちろん問題ない
  • すでにHTMLにOGPが設定されていた場合、重複するという問題が発生する
  • </body> を置き換えるというナイーブな実装

まとめ

以下のようにTwitter上で設定したOGPが表示されました。

f:id:Yuiki0627:20200730113058p:plain

※ちなみにこの曲一覧はテスト用なので、本当に僕がよく聴いている曲ではないです。

画像生成については、様々な方法があるので、よしなにやってみてください。

今回は、enPiTという大学の授業でチームで実装したアプリに、OGPを設定しようと思っていろいろやったので、せっかくなので記事化しました。
作成したアプリ↓
https://musicle-app.web.app/