Gatsby.js製ブログで記事毎にOGP画像を自動生成する
2021/01/01
概要
Kentaro Matsushitaさんが開発されたOGP画像生成ライブラリ「catchy-image」を使用して、Gatsby.js製ブログにOGPを自動生成・設定する実装をしました。お手軽に記事タイトルに応じたOGP生成ができる素晴らしいライブラリです。gatsby-starter-blogをベースに構築したブログを想定し、導入までの作業をメモしておきます。
完成したOGP画像の例
記事のタイトルとサイト名、アイコンが記載されたシンプルなOGP画像が完成しました。
執筆時構成
- node
- v12.15.0
- gatsby
- 2.27.5
- catchy-image
- 0.1.6
最新の情報はcatchy-imageリポジトリのREADMEをご参照ください。
手順
catchy-imageをインストール
catchy-image
パッケージをインストールします。
npm install --save catchy-image
Gatsby.jsでビルド時にOGP画像を生成するプラグインを作成する
catchy-image は与えられたメタデータ (Title, Author, Iconなど) の情報を元にOGP画像を自動生成するライブラリです。 そのため、Gatsbyの記事に応じてTitleなどを設定するため、Gatsbyプラグインを自作する必要があります。
でもプラグインを作成するといっても難しいことはしません。
数行の index.js
と package.json
を作成し、配置するだけです。
今回は公式に倣って自作プラグインの名前を gatsby-remark-og-image
として設定します。
詳しいプラグインの作り方はGatsby公式のドキュメントを参照するとよいでしょう。
Creating a Remark Transformer Plugin
ディレクトリ構成
リポジトリルートに plugins
というディレクトリを作成して、その中にプラグイン名のディレクトリを作成します。
そしてさらにその中に index.js
と package.json
を配置します。
gatsby-config.js
...
plugins
└ gatsby-remark-og-image
├ index.js
└ package.json
index.jsの中身
catchy-image 作者の方のブログ記事に書いてあるものをそのまま持ってくれば動きます。
私は画像生成先のディレクトリを1階層深くしたかったので少しだけいじっています。
gatsby-config.js
に設定を書きますが、ここで参照していないものは無効なので要注意です。
具体的には、options.output.directory
を gatsby-config.js
内で変更しても、ライブラリの実行部分(index.js
)で参照されていないと何も変わりません。
const catchy = require('catchy-image')
module.exports = async ({ markdownNode }, pluginOptions) => {
// gatsby-config.jsの設定情報とマークダウンのメタデータを画像生成ライブラリの引数に渡す
const result = await catchy.generate({
...pluginOptions,
output: {
...pluginOptions.output,
directory: `./public/blog/${markdownNode.fields.slug}`, fileName: pluginOptions.output.fileName,
},
meta: {
...pluginOptions.meta,
title: markdownNode.frontmatter.title
}
})
console.info(`gatsby-remark-og-image: Successful generated: ${result}`)
}
package.jsonの中身
外部公開の予定はなく、またエントリーポイントや名前が定義されていれば動くと思うので、最低限の情報だけ定義しました。
{
"name": "gatsby-remark-og-image",
"description": "Generate OGP image from article title.",
"version": "1.0.0",
"main": "index.js"
}
使用するフォントをリポジトリに追加する
私は Noto Sans JP の Bold を使いたいので Google FontsのNoto Sans JPのページからフォントファミリーをダウンロードしてきます。
保存したZIPファイルを解凍し、「NotoSansJP-Bold.otf」を取り出します。そして src/assets/fonts/
ディレクトリを作成し、そこにフォントファイルを設置します。
gatsby-config.jsを設定する
gatsby-config.js
の gatsby-transformer-remark
の plugin として、先ほど作った gatsby-remark-og-image
を設定します。
ここで設定した値は index.js
から pluginOptions
を介して呼び出します。
フォント、背景画像、アイコンなどは必要に応じて変更します。
記事タイトルの長さによってはタイトル文字列が入りきらない旨のエラーが発生します。
その場合は長いタイトルを短くするか、25行目のfontSize
を小さくしましょう。具体的に何文字以内まで大丈夫かは計測していませんが、fontSize=64
でエラーがでないくらいの長さが読みやすさ的にもちょうどよいと思います。
module.exports = {
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
{
resolve: `gatsby-remark-og-image`,
options: {
output: {
directory: '',
fileName: 'thumbnail.png'
},
image: {
width: 1200,
height: 630,
backgroundImage: './src/assets/images/og-background.jpg' //backgroundColor: '#e2eadd',
},
style: {
title: {
fontFamily: 'Noto Sans JP', fontColor: '#333333',
fontWeight: 'Bold',
fontSize: 64,
paddingTop: 100,
paddingBottom: 200,
paddingLeft: 150,
paddingRight: 150,
},
author: {
fontFamily: 'Noto Sans JP', fontColor: '#333333',
fontWeight: 'Bold',
fontSize: 42,
}
},
meta: {
title: '',
author: 'kyabe.net' },
fontFile: [
{
path: require.resolve('./src/assets/fonts/NotoSansJP-Bold.otf'), family: 'Noto Sans JP', weight: 'bold',
},
],
iconFile: require.resolve('./content/assets/profile-pic.png'), timeout: 10000,
},
},
],
},
},
],
}
og:imageメタタグを設定する
次のようなメタタグを設定します。
<meta property="og:image" content="https://kyabe.net/blog/gatsby-auto-generate-ogp-image/thumbnail.png" data-react-helmet="true">
ヘッダタグのコンポーネントの設定
React Helmetを使っている場合であれば、SEO用の共通コンポーネントで次のような image
引数をとれるようにしておくと柔軟かと思います。
下記はSEOコンポーネントの例です。image
指定がなければデフォルトの画像をOGP画像として設定するようにしています。
import React from "react"
import PropTypes from "prop-types"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
import ogp_image from "../../content/assets/default_og_image.jpg"
const SEO = ({ description, lang, meta, title, image }) => { const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
siteUrl
}
}
}
`
)
const titleText = `${title ? title + " | " : ""}${site.siteMetadata.title}`
const siteUrl = site.siteMetadata.siteUrl
const defaultImage = `${siteUrl}${ogp_image}`
return (
<Helmet
htmlAttributes={{
lang,
}}
title={titleText}
meta={[
// 中略
{
property: "og:image",
content: image || defaultImage, },
].concat(meta)}
/>
)
}
SEO.defaultProps = {
lang: `en`,
meta: [],
description: ``,
image: null,
title: ``
}
SEO.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string.isRequired,
image: PropTypes.string,
}
export default SEO
記事テンプレートの設定
上記SEOコンポーネントを blog-post.js
から呼び出します。
image
の実引数にはOGP画像への絶対パスを指定する必要があります。
相対パスだとうまく表示されないので気を付けましょう。
import React from "react"
// Components
import Layout from "../components/layout"
import SEO from "../components/seo"
const BlogPostTemplate = ({ data, pageContext, location }) => {
const post = data.markdownRemark
const siteTitle = data.site.siteMetadata.blogTitle
const { previous, next } = pageContext
return (
<Layout location={location} title={siteTitle}>
<SEO
title={post.frontmatter.title}
description={post.excerpt}
lang="ja"
image={`${data.site.siteMetadata.siteUrl}/blog${post.fields.slug}thumbnail.png`} />
// 中略
</Layout>
)
}
export default BlogPostTemplate
export const pageQuery = graphql`
query BlogPostBySlug($slug: String!) {
site {
siteMetadata {
blogTitle
siteUrl
}
}
markdownRemark(fields: { slug: { eq: $slug } }) {
// 中略
}
}
`