ブログサイトを運用する上でほぼ必須と言えるOGP画像を動的に生成するプログラムを作れたので解説します!
OGP画像を作る
とりあえずHTMLでそれっぽいものを作ってみましょう!
フォントは Noto Sans JP を使用しています。
※CSSは自分で書いてね★
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="style.css">
  <script src="./script.js"></script>
</head>
<body>
  <main>
    <p class="title">HTMLとPuppeteerで og:image を自動で生成するツールを作った話</p>
  </main>
</body>
</html>
上記のコードは title クラス に表示したいテキストを書いているだけです。
このままではHTML内に書いたテキストを表示しているだけなので、クエリパラメータからテキストを取得してそれを表示する仕組みを作ります。
document.addEventListener('DOMContentLoaded', async () => {
  const titleElem = document.querySelector('main .title');
  // クエリパラメータの `text` を指定
  titleElem.textContent = query()['text'];
});
// クエリパラメータを使いやすくするやつ
function query() {
  const queryStr = decodeURI(window.location.search.slice(1));
  const queries = {};
  if (!queryStr) return queries;
  queryStr.split('&').forEach(queryStr => {
    const queryArr = queryStr.split('=');
    queries[queryArr[0]] = queryArr[1];
  });
  return queries;
}
とても簡単な仕組みで、クエリパラメータの text (?text=文字列) を取得して、それを title クラス に指定しているだけです。
これでクエリパラメータを変えるだけで表示したい文章を変えるようにできました。
Puppeteerでスクショする
Puppeteerとは
Puppeteer is a Node.js library which provides a high-level API to control Chrome/Chromium over the DevTools Protocol. Puppeteer runs in headless mode by default, but can be configured to run in full ("headful") Chrome/Chromium.
PuppeteerはNode.jsライブラリで、DevToolsプロトコル上でChrome/Chromiumを制御するための高レベルAPIを提供する。Puppeteerはデフォルトではヘッドレスモードで動作しますが、完全な("headful")Chrome/Chromiumで動作するように設定することもできます。(DeepL翻訳)
つまり、ブラウザを立ち上げなくてもブラウザの操作ができるよーってことです!(多分)
インストール
今回はサーバーを立てるため express もインストールします。
npm i puppeteer express
ディレクトリ構成
├─ node_modules/
├─ public/
  ├─ screenshots/
  ├─ index.html
  ├─ style.css
  └─ script.js
├─ index.js
├─ package.json
コード
const puppeteer = require('puppeteer');
const express = require('express');
const app = express();
// publicフォルダ内のファイルを返せるようにする
app.use(express.static('public'));
app.get('/ogp-image', async (req, res) => {
  const text = req.query.text;
  return res.sendFile(await screenshot(text));
});
app.listen(14444, () => {
  console.log('サーバーを起動しました');
});
async function screenshot(text) {
  // スクリーンショットを撮りたいURL
  const url = `http://127.0.0.1:14444/?text=${text}`;
  // スクリーンショットを保存する場所
  const screenshotPath = __dirname + '/public/screenshots/image.png';
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  // OGP画像は 1200x630px が最適なサイズ
  page.setViewport({ width: 1200, height: 630 });
  // networkidle0: 500msの間、コネクション数が0だった場合、移動完了とする
  await page.goto(url, { waitUntil: 'networkidle0' });
  // スクリーンショットを撮る
  await page.screenshot({ path: screenshotPath });
  await browser.close();
  return screenshotPath;
}
実行してみよう!
node index.js
実行したら http://127.0.0.1:14444/ogp-image?text=表示したいテキスト にアクセスしてみてください。恐らく3秒程度で画像が返されると思います。
こんな感じになるよ
ちょっといじったらこんな感じに生成できます
最後に
最後まで読んでいただきありがとうございます!
後半は解説が適当になった気がしますが許してください…


