LoginSignup
115
107

More than 1 year has passed since last update.

Vue.js + Firebase + OGP画像生成 本当に爆速で作れた!

Last updated at Posted at 2020-01-31

自己紹介

プログラマーのAkasaと申します。
夢を実現するために頑張ってる人を応援するサービスを模索しています!
今回、Vue.js, Firebase (Hosting, Functions, Storage)でOGP画像生成形サービスを爆速で作りました!
(サービスは現在停止しています)

作ったサービス

  • Wishlist Share
    • Amazonのほしい物リストを画像付きでツイートできるサービス。

開発環境

  • firebase : ^7.6.0
  • vue": ^2.6.6

参考にした記事

ページ

リンクを踏む側サンプル

リンクを作る側① リンクを作る側② リンクを踏む側

Functions設定

  • parseAmazon

    • ユーザーに入力してもらったほしい物リストのリンクから、該当のページをパースし商品画像などのデータを取得する。(省略)
  • buildHtml

    • SPAで動的にOGPを設置する為にはテクニックが必要で生のHTMLを返すFunctionsを用意する。
    • HeaderにTwitterBot用のOGPを設置する。
    • BodyにはTwitterでリンクをタップした人に見せたいページのhrefを設置し、そこに遷移させる。

アーキテクチャ

  • FirebaseCloudStorageの構成に合わせてツイートに載せるリンクが変わる。
  • 今回のディレクトリ構成は/amazon/TYPE/ID.pngなので URLにはTYPEとIDのみ入れ込み、残りはFirebaseCloudStorageのメタデータに入れておく。

Diagram.png

ex)

  • URL

    • TYPE : 1 (まだ背景画像を1つしか用意してないので1のみ)
    • ID : O5Z11NOGY2KH (リストのID)
  • METADATA

    • AVATAR_URL : https://images-fe.ssl-images-amazon.com/images~~~~
    • LIST_ NAME : "今年中に読みたい技術書"
    • LIST_DESCRIPTION : "Wishlist Share開発者Akasaのほしい物リストです!継続は力なり!読むぞー!"
    • 
DOMAIN : jp (amazonのドメイン、他にもco.ukとかがある)

コード

build-html.js
const functions = require('firebase-functions');
const util = require('util');
const SITEURL = 'https://wishlist-share.com';
const OGP_W = 1200;
const OGP_H = 630;
const SITE_NAME = 'Wishlist Share';
const { admin } = require('./firebase/firebase-admin');
const { generateRandomStrings } = require('./utils/randomStrings');

const func = functions.https.onRequest(async (req, res) => {
  // ex) req.path = /amazon/1/2H6Q88D0SBG0E
  const [,, type, id] = req.path.split('/');

  const pageUrl = SITEURL + req.url;
  let html;
  let listName;
  let desc;
  let link;

  // ex) https://wishlist-share.com/invited/amazon/1/2H6Q88D0SBG0E
  link = `https://wishlist-share.com/invited/amazon/${type}/${id}`;

  const path = `amazon/${type}/${id}.png`; // ex) /amazon/1/39QHNEX56SK42.png

  const bucket = admin.storage().bucket('amaz0n-wish-list.appspot.com');
  const file = bucket.file(path);
  const exists = await file.exists();

  if (!exists[0]) {
    res.status(404).end();
    return;
  } else {
    const getMetadataAsync = util.promisify(file.getMetadata).bind(file);
    const metadata = await getMetadataAsync().catch(e => {
      console.log(e);
      throw e;
    });

    listName = metadata.metadata.listName;
    desc = metadata.metadata.desc;
  }
  const randomStrings = generateRandomStrings(5); // For Web crawlers fetching every time.
  const imageUrl = "https://firebasestorage.googleapis.com/~~~~";
  html = createHtml(
    /* title    */ listName,
    /* desc     */ desc,
    /* imageUrl */ imageUrl,
    /* link     */ link,
    /* pageUrl  */ pageUrl
  );

  res.set('Cache-Control', 'public, max-age=3600, s-maxage=3600'); // 1 hour
  res.status(200).send(html);
  return;
});


const createHtml = (title, desc, imageUrl, link, pageUrl) => {
  return `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Wishlist Share</title>
    <meta property="og:title" content="${title}" />
    <meta property="og:image" content="${imageUrl}" />
    <meta property="og:image:width" content="${OGP_W}" />
    <meta property="og:image:height" content="${OGP_H}" />
    <meta property="og:description" content="${desc}" />
    <meta property="og:url" content="${pageUrl}" />
    <meta property="og:type" content="article" />
    <meta property="og:site_name" content="${SITE_NAME}" />
    <meta name="twitter:site" content="${SITEURL}" />
    <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="${desc}" />
  </head>
  <body>
   <script>
    location.href = '${link}';
    </script>
  </body>
</html>
`;
};

module.exports = func;

躓きやすいポイント

firebase.jsonとrouter.jsを載せておきます。
設計に応じて書き換えてください。

firebase.json
{
  "hosting": {
    "public": "dist",
    "rewrites": [{
        "source": "/parseAmazon",
        "function": "parseAmazon"
      },
      {
        "source": "/*/*/*",
        "function": "buildHtml"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
}

router.js
import Vue from 'vue';
import Router from 'vue-router';
import Main from './views/Main.vue';
import Invited from './views/Invited.vue';

Vue.use(Router);

export default new Router({
  mode: 'history',
  linkExactActiveClass: 'active',
  routes: [
    {
      // リンクを作る側
      path: '/',
      name: 'main',
      component: Main
    },
    {
      // リンクを踏む側
      path: '/invited/amazon/:type/:id',
      name: 'invited',
      component: Invited
    },
  ]
});

便利なサービス

Card validator
ngrokが便利すぎる

115
107
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
115
107