2020年3月25日 更新

【SNSシェア効果アップ!!】Netlify × Nuxt.jsでOGP画像を動的生成しよう!

最近はSNSからのWebサイトへの流入もかなり多くなりましたね。
どのサイトに行ってもソーシャルシェアボタンはもはや当然のように設置されているのではないでしょうか。

実際にSNSシェアされた時にURLや文字でシェアされるだけでも効果はあるかと思いますが、どうせなら最適化された画像を用意して視覚的にもアピールしたいですよね!でもページごとに自作するのは大変。。

そこで今回は、Nuxt.jsの静的サイト(またはSPA)で、Netlifyデプロイ時にOGP画像を動的に生成する手順を説明したいと思います。
これが実装出来れば原稿データ、及び画像ファイルを用意するだけで自動で作成できてしまいます!

それでは解説していきます。

必要なライブラリを追加しよう!

必要なライブラリは3つです。

fs

Node.jsでファイル操作を行うライブラリです。ホスト上にある原稿データ(JSONファイル)を取得するために使用します。
下記コマンドでインストールします。

npm install fs


sharp

Node.jsで画像操作を行うためのライブラリです。多機能、かつ高速で優秀なライブラリです。(だと思ってます。笑
今回は画像のリサイズ、及び合成に使用します。
下記コマンドでインストールします。

npm install sharp


text-to-svg

Node.jsで文字列をSVGの画像データに変換する事ができるライブラリです。
原稿データのタイトルを画像に変換するために使用します。
下記コマンドでインストールします。

npm install text-to-svg


以上でライブラリの準備は完了です。

モジュールを準備しよう!

ここでは画像生成用のモジュールの解説をしていきます。
今回の構成は下記を想定します。(関連しないものは省略しています。)

data/
└page1.json    #原稿データ
└page2.json    #原稿データ
modules/
└ogpGenerater.js    #画像生成モジュール
pages/
└_slug.vue    #OGP対応させたい動的ルーティングページ
static/
└font/
 └NotoSerifJP-Medium.otf    #text-to-svgでフォント指定する際のフォントファイル
└img/
 └image1.png    #合成に使用するベース画像
 └image2.png    #合成に使用するベース画像
└ogp/
 └.gitkeep    #合成画像の出力先
nuxt.config.js    #Nuxt.jsの設定ファイル


まず、元となる原稿データを用意します。こんな感じ。
data/page1.json

{
 "slug": "page1",
 "title": "タイトル1",
 "description": "説明1",
 "thumbnail": "image1.png"
}

data/page2.json

{
 "slug": "page2",
 "title": "タイトル2",
 "description": "説明2",
 "thumbnail": "image2.png"
}


次に画像生成モジュールを用意します。
modules/ogpGenerater.js

import fs from 'fs'
import sharp from 'sharp'
import TextToSVG from 'text-to-svg'

const generateOGP = function() {
 const textToSVG = TextToSVG.loadSync('./static/font/NotoSerifJP-Medium.otf')
 const fileNames = fs.readdirSync('./data')
 for (const key in fileNames) {
  const file = JSON.parse(fs.readFileSync('./data/' + fileNames[key], 'utf8'))
  const textSvg = textToSVG.getSVG(file.title, {
   x: 0,
   y: 0,
   fontSize: 100,
   anchor: 'top',
   attributes: { fill: 'black', stroke: 'white' }
  })
  sharp('./static/img/' + file.thumbnail)
   .composite([
    {
     input: Buffer.from(textSvg)
    }
   ])
   .resize(1200, 630)
   .toFile('./static/ogp/' + file.slug + '.png', (error) => {
    // eslint-disable-next-line no-console
    if (error) console.log('OGP Generate Error: ' + error)
   })
 }
}

module.exports = function() {
 this.nuxt.hook('generate:before', (generator) => {
  // eslint-disable-next-line no-console
  console.log('OgpGenerater:start')
  generateOGP()
  // eslint-disable-next-line no-console
  console.log('OgpGenerater:finish')
 })
}


generateOGP関数で、原稿データを読み取ってタイトル文字列の画像を作成し、サムネイル画像と合成してリサイズして出力、という要領です。
その関数をgenerate:beforeのタイミングで実行されるようフックしています。
フックについての詳細はこちら

次に、フォントファイルstatic/font配下に用意します。
今回はNotoSerifJP-Medium.otfを使いましたが、お好みのフォントファイルで構いません。
日本語ならGoogle Fontのこことか。

あと、背景に使用する画像ファイル
static/img/image1.png
static/img/image2.png

OGP画像の出力先フォルダ(static/ogp)
※空ディレクトリコミット用にstatic/ogp/.gitkeepの空ファイルも作っておきます。

あと、動的ルーティングページを用意します。
pages/_slug.vue

<template>
 <div class="container">
  <div>
   <p>
    <img :src="baseUrl + '/img/' + page.thumbnail" />
   </p>
   <hr />
   <h1 class="title">
    {{ page.title }}
   </h1>
   <hr />
   <p>
    <img :src="baseUrl + '/ogp/' + page.slug + '.png'" />
   </p>
  </div>
 </div>
</template>

<script>
export default {
 async asyncData({ params, error, payload }) {
  if (payload) {
   return { page: payload }
  } else {
   return { page: await require(`~/data/${params.slug}.json`) }
  }
 },
 data() {
  return {
   baseUrl: process.env.baseUrl
  }
 },
 head() {
  return {
   title: this.page.title,
   meta: [
    {
     hid: 'description',
     name: 'description',
     content: this.page.description
    },
    {
     hid: 'og:type',
     property: 'og:type',
     content: 'article'
    },
    {
     hid: 'og:title',
     property: 'og:title',
     content: this.page.title
    },
    {
     hid: 'og:url',
     property: 'og:url',
     content: this.baseUrl + '/' + this.page.slug + '/'
    },
    {
     hid: 'og:description',
     property: 'og:description',
     content: this.page.description
    },
    {
     hid: 'og:image',
     property: 'og:image',
     content: this.baseUrl + '/ogp/' + this.page.slug + '.png'
    },
    {
     hid: 'twitter:card',
     name: 'twitter:card',
     content: 'summary_large_image'
    }
   ]
  }
 }
}
</script>

<style>
.container {
 margin: 0 auto;
 min-height: 100vh;
 display: flex;
 justify-content: center;
 align-items: center;
 text-align: center;
}
.title {
 font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
  'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
 display: block;
 font-weight: 300;
 font-size: 100px;
 color: #35495e;
 letter-spacing: 1px;
}
</style>


head()内でOGPに関するメタタグを設定しています。

最後に、nuxt.config.jsの設定をします。(大事なところだけ記載します。)

export default {
 mode: 'universal',
 modules: [
  '@/modules/ogpGenerater'
 ],
 env: {
  baseUrl: process.env.URL || 'http://localhost:3000'
 },
 generate: {
  routes() {
   const routes = []
   const fs = require('fs')
   const fileNames = fs.readdirSync('./data')
   for (const key in fileNames) {
    const page = JSON.parse(
     fs.readFileSync('./data/' + fileNames[key], 'utf8')
    )
    routes.push({
     route: '/' + page.slug,
     payload: page
    })
   }
   return routes
  }
 }
}


  • プリレンダリングするためmodeはuniversalモード
  • OGP作成モジュールをmodulesに記載
  • generateで動的ページの出力設定

という感じです。
SPAモードの場合でもNetlifyのビルド設定からプリレンダリング機能を有効にすれば出来ます。その場合はgenerate設定は不要です。
Netlifyプリレンダリング設定

以上でモジュールの準備は完了です!
ローカル環境で下記コマンド実行してみてください。

npm run generate


するとstatic/ogp配下にこのような画像が出来ているはずです。
生成されたOGP画像

この状態で下記コマンドを実行すれば画面(http://localhost:3000/page1)でも確認できるようにしておきました。

npm run dev

背景画像と文字と合成後画像

ここまで確認できたらGitHubにプッシュしてデプロイしてみましょう!

デプロイ後、画面を確認し問題が無ければ、Twitter Card validatorへアクセスして確認したいURLを入力してプレビューボタンを押してみます。
下記のように表示されましたか?


ここまで出来れば問題なし!
あとはTwitterにでもURL貼り付けてみましょう!
せいっ!


はい!これで解説は終わりです。
参考までに、GitHub、及びWebサイト載っけときます。
説明不要な方はモジュールだけ見てくださいな。←ここで言うな。

今回は分かりやすくホストサーバー上に存在するファイルで画像生成しましたが、
外部の原稿データ(APIデータ)や画像ファイル使う場合はfsの代わりにaxiosなど使用しても出来ます。

アレンジ効かせて皆さんもOGP職人目指しちゃいましょう!
それでは!!