Vue.js+firebaseでSSRなしにOGP対応する方法

投稿者: | 2019年3月2日

firebase+Vue.jsでWebサービスの運用をしているのですが、サクッと作ったサービスなのでサーバーサイドレンダリング(SSR)対応ができていません。SSRする目的ですが主にTwitterやfacebookといったSNS向け(OGP)だと思っています。GoogleのクローリングではSSRなしでも一応Indexはされますので。Googleの評価に悪影響がある可能性はありますが…

とはいえ、SNSにおける拡散、SNSからの流入というのは個人運営のWebサービスでは無視することもできず、SSRなしでの対応をやってみました。けっこう苦戦しましたがようやく実現できましたので実装方法を共有させていただきます。

本記事はQiitaの以下の記事を激しく参考にしています。(というか、ほぼ同じ)

Firebase + SPA で SSR なしに OGP 対応

しかし、Qiitaの記事そのままでは私のアプリでは動作しなかったので若干の変更を加えています。

対応内容の概要

  1. firebase.jsonに設定を追記し、特定のURLでHTTPリクエストが来た場合、firebase functionsを起動させます。
  2. firebase functionsでmetaタグのみを返却する関数を作成します。ここで、bodyタグにはredirect処理を設定し、ユーザーアクセスの場合はページを表示できるようにします。
  3. Vue-routerのindex.htmlに追記
  4. App.vue(SNSからのリンクで表示するぺージ)でURLの書き換えを行います

①firebase.jsonに設定を追記し、特定のURLでHTTPリクエストが来た場合、firebase functionsを起動させる

{
  "functions": {
    "predeploy": []
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/c/*",
        "function": "note"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

 

私のfirebase.jsonの全文です。今回追加しているのはrewrites内のfunctions: noteの記載です。urlに「/c/」を含む場合、firebase functoinsのfunction名「note」を起動するという意味の記述になります。

②firebase functionsでmetaタグのみを返却する関数を作成

function buildHtmlWithPost(id, obj) {
return `<!DOCTYPE html><head>
<title>${obj.title}の年表|平成年表</title>
<meta property="og:title" content="${obj.title}の年表|平成年表">
<meta property="og:image" content="${img}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${obj.title}の年表|平成年表">
<meta name="twitter:description" content="${obj.text}">
<meta name="twitter:image" content="${obj.img}">
<link rel="canonical" href="/c/${id}">
</head>
<body>
  <script>window.location="/c?id=${id}";</script>
</body>
</html>`
}

exports.note = functions.https.onRequest(function(req, res) {
  //URLからidを抽出
  const path = req.path.split('/')
  const noteId = path[path.length-1]
  //idをキーにfirestoreからデータを取得
  db.collection('chronology').doc(noteId).get()
  .then((doc) => {
    let obj = doc.data()
    //返却用のHTMLを取得
    const htmlString = buildHtmlWithPost(noteId, obj)
    res.set('Cache-Control', 'public, max-age=300, s-maxage=600')
    res.status(200).end(htmlString)
  }).catch((err) => {
    res.status(500).end(err)
  })
})

 

こちらがfirebase functionsとして作成した「note」という関数になります。

ポイントは、内部関数「buildHtmlWithPost内の以下です。

<body>
  <script>window.location="/c?id=${id}";</script>
</body>

 

ユーザーからのアクセスの場合にページを表示させるためにリダイレクト処理を記述しています。リダイレクト先のURLがポイントで、「/c?id=${id}」としました。「/c/?id=${id}」としてしまうと、①のfirebase.jsonの設定のため、再びfunctions 「note」が呼ばれてしまい正常に動作しません。「/c?id=${id}」とすることでfunctionsではなく、通常のhostingが呼ばれるようになります。

③vue-routerのindex.htmlに追記

  {
    path: '/c/:id',
      name: 'Chronology',
      component: Chronology
    },
    {
      path: '/c',
      name: 'Chronology_',
      component: Chronology
    }

追加したのは「/c]のpathです。「/c」でアクセスがあった場合のコンポーネントへのルーティングを追記します。

④App.vue(SNSからのリンクで表示するぺージ)でURLの書き換え

created () {
  if (this.$route.query.id) {
    this.$router.replace('/c/' + this.$route.query.id)
  }
},

「?id=hogehoge」で来た場合に、URLを「/hogehoge」に書き換える処理です。

おわりに

おそらくこの対応でうまく動くかと思います。私のサイトは動いてると思われますので…

SNS用に個別に処理を組まないといけないというのも辛いなあと思いますが、ユーザー獲得のためにはやむを得ない状況です。みなさまの問題解決に少しでもお役に立てていれば幸いです。

 


カツオが開発したWebサービスです。

「セールサーチ」ネットショップのセール情報の検索サイト!

平成の想い出を気軽に年表にしてシェアすることができるサービスです。ぜひ使ってみてください。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です