👋

Nuxt.js×Vercelで動的OGPを実現する

2021/03/07に公開

こんにちは、Yuiです。

今回こちらのサービスをリリースしました。

https://twitter.com/yui_active/status/1366738405904642048?s=20

ここで一番困ったのが動的OGPです。
このサービス自体は丸1日ぐらいで組んだんですが、動的OGP部分に2日ぐらいかかりました。笑

もう二度と動的OGPで悩みたくないのでまとめて書きたいと思います。

nuxt.config.jsでデフォルトのOGP情報を書く

nuxt.config.js
export default {
  head: {
    title: '塗り絵ツクール',
    htmlAttrs: {
      lang: 'ja',
      prefix: 'og: http://ogp.me/ns#',
    },
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: '塗り絵をかんたんに作れるサービスです',
      },
      {
        hid: 'og:site_name',
        property: 'og:site_name',
        content: '塗り絵ツクール',
      },
      { hid: 'og:type', property: 'og:type', content: 'article' },
      {
        hid: 'og:url',
        property: 'og:url',
        content: 'https://nurie-maker.com',
      },
      { hid: 'og:title', property: 'og:title', content: '塗り絵ツクール' },
      {
        hid: 'og:description',
        property: 'og:description',
        content: '塗り絵をかんたんに作れるサービスです',
      },
      {
        hid: 'og:image',
        property: 'og:image',
        content: {OGP画像のURL},
      },
      { name: 'twitter:card', content: 'summary_large_image' },
      { name: 'twitter:site', content: '@yui_active' },
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
  },
  
  components: true,

SSRにするためにmodeはuniversalにしないといけないとずっっっと思ってたんですが、ちゃんと見るとデフォルトでuniversalなので不要でした。笑
https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-mode/

動的OGP用ページに任意のパラメータをつけてURL発行

今回、動的OGPのために、以下の流れで行いました。

  • シェアボタンを押すとモーダルを開く
  • uuidを発行
  • S3にuuid.jpgの名前で画像をアップロード
  • 画像のアップロードが確認できてからシェア用リンクを発行

uuidの発行方法は何でも良いのですが、私の場合は以下のようにしてます。

index.vue
    generateUuid() {
      let chars = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.split('')
      for (let i = 0, len = chars.length; i < len; i++) {
        switch (chars[i]) {
          case 'x':
            chars[i] = Math.floor(Math.random() * 16).toString(16)
            break
          case 'y':
            chars[i] = (Math.floor(Math.random() * 4) + 8).toString(16)
            break
        }
      }
      return chars.join('')
    },

そしてシェアボタンを押したときのアクションがこちら

index.vue
    async OpenTwitterModal() {
      this.twitterModalFlag = true
      this.uuid = this.generateUuid()
      await postImageData(this.uuid, this.nurieData).then(() => {
        this.isFetched = true
        window.history.pushState(null, null, `/nurie/${this.uuid}`)
      })
    },

postImageDataはAWSのS3に画像をアップロードするために書いた関数です。
postImageData(uuid, 画像データ)と引数を取ることで、画像データをuuid.jpgの形式でS3に保存しています。

isFetchedフラグは念の為につけたのですが、画像が確実にアップロードしてからツイートできるようにしたいのでつけました(後述します)。

そしてwindow.history.pushStateを使うことで、モーダルを開いてURLを変えるものの遷移はしないという動きを実現しています。

ここまでの動きが以下です。
シェアボタンを押すとモーダルが開き、少ししたあとにURLが変更していることがわかるかと思います。

Image from Gyazo

ツイートボタンを押すとリンクをシェアできるようにする

ツイートボタンを押したときのために、computed内で先程発行したシェア用のURLを作りました。
そしてそのURLを利用して、twitterURLとfacebookURLを先に整形しておきます。

index.vue
  computed: {
    url() {
      return `{baseUrl}/nurie/${this.uuid}`
    },
    twitterURL() {
      return (
        `https://twitter.com/intent/tweet?url=${this.url}&text=` +
        encodeURIComponent(
          `塗り絵ツクールで塗り絵を作ったよ\r\n #塗り絵ツクール`
        )
      )
    },
    facebookURL() {
      return `https://www.facebook.com/sharer/sharer.php?u=${this.url}&t=塗り絵ツクールで塗り絵を作ったよ\n#塗り絵ツクール`
    },
  },

あとはシェアボタンを押したときに、それぞれのURLに飛ばせば良いのですが、画像がアップロードされる前にこのURLに飛んでしまうとOGPが実現できないので、先程のisFetchedフラグを使います。

index.vue

    tweetButton() {
      if (this.isFetched) {
        window.open(this.twitterURL, '_blank')
      }
    },

今はモーダルを表示してからしかツイートボタンは出ないので、よっぽど光の速さでボタンを押されない限りはこのisFetchedフラグはなくても問題ないのですが(ほぼ確実に画像アップロードのほうが速いので)念のためにつけています。
もし構成上確実に必要ないようなら教えて下さい。

各ページでOGPの上書きをする

さて、今回は/nurie/uuidというリンクを発行して動的OGPを実現しているため、pages/nurie/_id.vueを作成して、_id.vueでheadタグの上書きをします。

_id.vue
export default {
  head() {
    return {
      meta: [
        { hid: 'og:url', property: 'og:url', content: {OGP用のURL} },
        { hid: 'og:image', property: 'og:image', content: {OGP画像のURL} },
      ],
    }
  },

ページ数が増えるようならこの部分はmixinsで設定したほうがシンプルですが、今回はこの_id.vue以外でOGPの書き換えをすることは想定していないのでべた書きしています。
また、今回、https://xxxxx.s3-ap-northeast-1.amazonaws.com/xxxxx/${uuid}.jpg の形で画像を保存しているのが決まっているため、画像の取得にAPIを接続する必要はなく、直接画像URLを書くことができたのですが、もしAPIで取得した画像をOGPに設定するのなら、asyncData内で取得して利用しないといけないらしいです。

※ちなみにこの設定で、twitter、facebookともにOGPの設定はうまくいっているのですが、なぜかslackだけデフォルトのOGPで展開されてしまいます......。なのでもしかしたらこの部分の書き方も少し違うのかもしれませんが、参考までにお願いします。

vercel.jsonを作成する

vercel.jsonは最初からvercelを介してリポジトリを作ってる人はあらかじめ作成されているファイルになると思うのですが、完成させてからvercelにアップした場合は自分で作成しないといけません。

そして今回この作成をしていなかったことから、なぜかOGPが表示されないとしばらく悩むことになってしまいました....。

vercel.jsonは公式そのままの書き方です。

vercel.json
{
  "version": 2,
  "public": true,
  "builds": [
    {
      "src": "nuxt.config.js",
      "use": "@nuxtjs/vercel-builder",
      "config": {
        "serverFiles": ["package.json"]
      }
    }
  ],
  "routes": [
    {
      "src": "/_nuxt/.+",
      "headers": { "Cache-Control": "public, max-age=31536000" }
    },
    {
      "src": "/sw.js",
      "dest": "/_nuxt/static/sw.js",
      "headers": {
        "cache-control": "public, max-age=43200, immutable",
        "Service-Worker-Allowed": "/"
      }
    },

    {
      "src": "/(.*)",
      "headers": { "cache-control": "s-maxage=1, stale-while-revalidate " },
      "dest": "/"
    }
  ]
}

あとはデプロイしたら動的OGPが実現されているかと思います。

おまけ

ちなみに、もともと個別ページは作るつもりがなかったので、OGP用のリンクにアクセスされたらトップページにリダイレクトをかけるようにしてたんですが、Nuxt.jsのライフサイクル的に、mounted内でリダイレクト処理をかけるとOGPが書き換わる前にリダイレクトしてしまうようなので、OGPを書き換えてからなにかしたい場合はcreated内で動かさないといけないみたいでした。

現在は個別ページを作ったので、シェアされたOGPからは個別のページにアクセスできるようにしています。
今後はこれに色塗り機能も実際に付けていきたいと思っています!

Image from Gyazo

それでは最後までお読みいただきありがとうございました!
まさかり大歓迎なので、なにか間違えていたらどんどんご指摘ください!

Discussion