Gatsbyで動的に画像を生成する
なぜ自動生成したか
ブログを書くにあたり、サムネイル・OGを探したり作ったりする必要がありました。 Qiitaのようにただ記事を書きたいのに、画像探しなど他のところに時間を使うのが嫌でした。
なので自動化しよう!とおもいました。
仕組み
仕組みは簡単で、Contentfulからページデータを取得したときに外部のAPIに記事タイトルを渡し画像化します。
外部のAPIはnodejsで実装してVercelでデプロイしました。 Vercelにog-imageを作成できるパッケージがあるので、これをベースにしました。
やり方
APIの構築はほぼforkで使えるのでReadmeを読んでください。 PuppeteerでスクショしてるのとTS使って書かれてるぐらいなので特に難しくはなかったです。
Gatsbyでの実装
gatsby-node.js
のcreatePages
内でリクエストを飛ばします。
const { createRemoteFileNode } = require(`gatsby-source-filesystem`);
const { fluid } = require(`gatsby-plugin-sharp`);
exports.createPages = async ({ graphql, actions, getCache, createNodeId, cache, reporter }) => {
const { createPage, createNode } = actions;
const result = await graphql(
`
{
allContentfulBlogPost(sort: { fields: [publishDate], order: DESC }) {
edges {
node {
id
slug
title
}
}
}
}
`
)
if (result.errors) {
throw result.errors
}
// Create blog posts pages.
const posts = result.data.allContentfulBlogPost.edges
// ここで外部リクエストして画像を受けとっている
const externalFluidImages = await generateImageFromPageTitle(posts, getCache, createNode, createNodeId, reporter, cache);
const blogPostTemplate = path.resolve(`./src/templates/post.js`)
posts.forEach(post => {
createPage({
path: `post/${post.node.slug}`,
component: blogPostTemplate,
context: {
id: post.node.id,
slug: post.node.slug,
externalFluidImage: externalFluidImages.get(post.node.slug),
},
})
})
}
gatsby-source-filesystem
はローカルの画像をGatsbyで扱いやすくするのに使われますが、外部の画像を取り込むこともできます。
gatsby-plugin-sharp
は画像Webpにしてくれます。
なので、require
しています。
あとは、クエリからブログを作ってます。
const externalFluidImages = await generateImageFromPageTitle(posts, getCache, createNode, createNodeId, reporter, cache);
ここは長くなったので別メソッドに分けてます。 内容は下記になります。
async function (pages, getCache, createNode, createNodeId, reporter, cache) => {
const featureImages = new Map();
for (page of pages) {
const { node } = page;
let url = `${process.env.OPEN_GRAPH_GENERATE_API}${encodeURIComponent(node.title)}.png?md=1&fontSize=100px&background&fontColor=#777`
if (await cache.get(node.title) || featureImages.has(node.slug)) {
continue;
}
const fileNode = await createRemoteFileNode({
url: url,
parentNodeId: node.id,
getCache,
createNode,
createNodeId,
name: node.title
});
const generatedImage = await fluid({
file: fileNode,
reporter,
cache,
});
await cache.set(node.title, node.id);
featureImages.set(node.slug, generatedImage);
console.info('generate image from api', decodeURI(url));
}
return featureImages;
};
取得したページデータを引数にとり、ループしてます。
gatsby-source-filesystem
はcreateRemoteFileNode
でリモートファイルをダウンロードし、サイトのGraphQLに追加してます。
メソッドの詳細はこちら
取得した画像をWebp化してMapに突っ込んで返却しています。 一度作成したものはキャッシュにいれてもう生成しないようにします
あとは、ページでpageContext
から受け取って使用します。
画像全部のMap自体をそのまま渡そうとしたらうまく行かなかったので、その場合はallImageSharp
から取れるので、クエリ使って取得してください。
const { allImageSharp } = useStaticQuery(graphql`
query {
allImageSharp {
nodes {
fluid(maxWidth: 1600) {
originalName
...GatsbyImageSharpFluid_withWebp
}
}
}
}
`)