サーバーレスで動的な OGP を設定する


サーバーレスで動的な OGP を設定する

SNS に自分の作ったページやサービスを配信する際に OGP を設定すると、リッチなコンテンツとして共有することができ、
CTR や CVR などに寄与するかもしれません。

OGP は html の meta タグとして設定しますが、要件によってはページごとに別のコンテンツとして設定したい場合が多々あります。
その場合 meta タグを動的に生成しなくてはならなくなって実装が大変です。

今回は Cloud Functions を用いて比較的簡単に動的な OGP を設定してみます。

構成

前提

  • /XXX は共有したいコンテンツで、XXX の部分は可変だと仮定
  • Twitter などに共有する際は /ogp/XXX というリンクを生成してそれをツイートする想定

流れ

  1. /ogp/XXX というリンクにアクセス(ここでいうリンクとはツイートにはられたリンクのこと)
  2. firebase hosting を使って /ogp から始まるパスはすべて Cloud Functions に受け流す
  3. Cloud Functions 側で bot かユーザーを UA から判断
  4. firestore や storage からデータを取得し bot やユーザーに対して適切なコンテンツを作る
  5. それぞれにあったコンテンツを返す

firebase hosting

以下のようにリダイレクトの設定を書きます。

firebase.json
 ...
    "rewrites": [
      {
        "source": "/ogp/**", "function": "ogp"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  ...

Cloud Functions

firebase をバックエンドに用いていたため Cloud Functions を使用しました。
isBot で Twitter bot かそれ以外かを判定します。
あとは firestore からもととなるデータを引っ張ってきて meta タグに設定します。

isBot = false の場合はアクセスされた url から /ogp の部分だけ取り除いた url にリダイレクトして元のコンテンツを返します。

index.ts
export const ogp = functions.region('us-central1').https.onRequest(async (req, res) => {
  const userAgent = req?.headers['user-agent']?.toLowerCase()
  const path = req.path
  const params = path.match(/^\/ogp\/contents\/(.+?)$/)
  const contentId = params?.pop()
  const doc = await db.collection('contents').doc(contentId).get()
  const name = doc.data()?.name
  const description = `${name} の説明`
  const imageUrl = doc.data()?.imageUrl

  const isBot = userAgent !== null && userAgent?.includes('twitterbot')

  if (isBot) {
    // Twitter bot の場合
    // ogp を設定した html を返す
    const html = createHtml(name, description, imageUrl, contentId)
    res.status(200).send(html)
  } else {
    // Twitter bot 以外(ユーザー)の場合
    // ogp を抜いたパスにリダイレクトしてあげる
    const url = `${DOMAIN}/contents/${contentId}`
    res.redirect(url)
  }
})

/**
 * ogp 作成用関数
 */
const createHtml = (
  title: string,
  description: string,
  imageUrl: string,
  contentId: string | undefined) => {
  return `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>OGP Test</title>
    <meta property="og:title" content="${title}" />
    <meta property="og:image" content="${imageUrl}" />
    <meta property="og:image:width" content="1200" />
    <meta property="og:image:height" content="630" />
    <meta property="og:description"${description}" />
    <meta property="og:url" content="${DOMAIN}/ogp/contents/${contentId}" />
    <meta property="og:type" content="article" />
    <meta property="og:site_name" content="test" />
    <meta name="twitter:site" content="${DOMAIN}" />
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:title" content="${title}" />
    <meta name="twitter:image" content="${imageUrl}" />
    <meta name="twitter:description" content="${description}" />
  </head>
  <body>For Twitter Bot</body>
</html>
`
}

テスト

開発中にツイートしてしまうとフォロワーさんに迷惑になっちゃうかもなので、 Twitter Card Validator を使ってテストします。
/ogp から始まる URL を貼り付けて設定したタイトルや説明、画像などが表示されたら成功です。

参考