HTMLとPuppeteerでOGP画像を動的に生成するプログラムを作った話

ブログサイトを運用する上でほぼ必須と言えるOGP画像を動的に生成するプログラムを作れたので解説します！


# OGP画像を作る
とりあえずHTMLでそれっぽいものを作ってみましょう！
フォントは Noto Sans JP を使用しています。

※CSSは自分で書いてね★

![labo.blue_ogp-image-generate_test.html_text=HTML%E3%81%A8Puppeteer%E3%81%A7%20og_image%20%E3%82%92%E8%87%AA%E5%8B%95%E3%81%A7%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E3%83%84%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%9C%E3%81%A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3142836/894ddced-0587-72b8-c37d-3198c3a9d09c.png)

```html:index.html
<!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内に書いたテキストを表示しているだけなので、クエリパラメータからテキストを取得してそれを表示する仕組みを作ります。


```javascript:script.js
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 (GitHub)](https://github.com/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. <br>
PuppeteerはNode.jsライブラリで、DevToolsプロトコル上でChrome/Chromiumを制御するための高レベルAPIを提供する。Puppeteerはデフォルトではヘッドレスモードで動作しますが、完全な（"headful"）Chrome/Chromiumで動作するように設定することもできます。(DeepL翻訳)

つまり、ブラウザを立ち上げなくてもブラウザの操作ができるよーってことです！(多分)


### インストール
今回はサーバーを立てるため express もインストールします。
```bash
npm i puppeteer express
```


### ディレクトリ構成
```
├─ node_modules/

├─ public/
  ├─ screenshots/
  ├─ index.html
  ├─ style.css
  └─ script.js

├─ index.js
├─ package.json
```


### コード
```javascript:index.js
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;
}
```


### 実行してみよう！
```bash
node index.js
```

実行したら http://127.0.0.1:14444/ogp-image?text=表示したいテキスト にアクセスしてみてください。恐らく3秒程度で画像が返されると思います。


### こんな感じになるよ
ちょっといじったらこんな感じに生成できます

![_C__Users_sorai_Documents_Dev_Server_labo.blue_pages_ogp-image-generate_index.html_text=HTML%E3%81%A8Puppeteer%E3%81%A7%20og_image%20%E3%82%92%E5%8B%95%E7%9A%84%E3%81%AB%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E3%83%84.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3142836/bf44b95d-40b3-6889-57dc-a7bbf91cd9cd.png)



# 最後に
最後まで読んでいただきありがとうございます！
後半は解説が適当になった気がしますが許してください…