みなさん、OGP画像を動的に生成してますか!?
今回、MeetGoGo(みんなの日程調整、みんなの割勘計算)のOGP画像を動的に生成することになり、色々と調査したことを共有したいと思います。
MeetGoGoのフレームワークにはRails、ローカル環境はDockerを使っており、本番サーバはAmazon Linux 2023 です。このような環境と相性の良いものを選ぶことにしました。
検討した方法
Railsを動かしているサーバ内でOGP画像を作る方法として次の2つを検討しました。
- Rails + ImageMagick + mini_magick を使う
- Rails + Chrome (Chromium) + Puppeteer + puppeteer-ruby を使う
mini_magick を使う方法で何が課題となったか
OGP画像の動的生成は ImageMagick と mini_magick を使ったやり方の方が、情報は多そうです。画像を合成したりテキストを埋め込んだり、比較的簡単にできます。しかし、次のことでハマりました。
『絵文字が表示できない』
最近は🍎や🐛などの絵文字も多く使われます。ユーザの入力したテキストをOGPにするという要件だったため、できれば絵文字を表示したい。しかし、MiniMagick ではそれがスムーズにいかない。
今回は、標準のテキストには「Noto Sans JP」、絵文字には「Noto Color Emoji」のフォントを適用しますが、MiniMagick を使ってテキストを画像に変換する時に1種類のフォントしか指定できません。Noto Sans JP を指定した場合、Noto Color Emoji フォントをフォールバックとして動作させる必要があります。
そのフォールバック設定を行っているのに、ローカル環境で絵文字が何故か出ない!苦しみながらたどり着いた結論は、使っていたDockerイメージ。そもそも、絵文字の出力ができる設定でライブラリがビルド、インストールされていないようでした。。Dockerで簡単に動かないという時点でかなりしんどいので 次に Puppeteer を検討しました。
Puppeteer なら絵文字も表示可能!
Puppeteer なら問題無く絵文字入りのテキストも埋め込んだ画像が作れました!
どのようなことを行ったか要点を説明します。
Dockerfile の修正
Puppeteer で画像生成するにあたり、Dockerコンテナで次のことを追加で行いました。
- Chromeのインストール
- Noto Sans JP、Noto Color Emoji のフォントインストール
Dockerイメージは ruby:3.4.1 を使っていたのですが、Chrome のインストールは次のようなコマンドになりました。
RUN apt-get update && apt-get install -y --no-install-recommends \ wget gnupg \ && wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \ && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list' \ && apt-get update && apt-get install -y google-chrome-stable \ && rm -rf /var/lib/apt/lists/*
フォントは、Googleフォントから ttf ファイルをダウンロードし、複数人で使いやすいようにS3にアップロードしてからそれをインストール(/usr/share/fonts/truetype/noto に保存)しました。
RUN apt-get update && \ apt-get install -y wget && \ mkdir -p /usr/share/fonts/truetype/noto && \ cd /usr/share/fonts/truetype/noto && \ # Noto Sans wget (s3のURL)/NotoSansJP-VariableFont_wght.ttf && \ # Noto Emoji wget (s3のURL)/NotoColorEmoji-Regular.ttf
背景画像を用意
OGP画像は 1200 x 630 px のサイズが推奨らしいです。そのサイズで次のような画像を作りました。

Puppeteer で読み込むHTMLを動的に生成
Puppeteer にHTMLを読み込む方法はテキストとして流し込む方法と、URLを指定する方法があります。今回は、テキストを流し込む方法を取ります。
CSSを使って、背景画像の上にテキストを表示すればOKです。背景画像はBase64で変換してHTMLに埋め込みましょう。次のようにしました。
# HTMLを生成 def self.make_html(title, image_path) # PNGファイルをBase64に変換 base64_image = Base64.strict_encode64(File.read(image_path)) html = <<~HTML <html> <head> <meta charset="utf-8"> <style> html, body { margin: 0; padding: 0; width: 1200px; height: 630px; font-family: 'Noto Sans JP', 'Noto Color Emoji', sans-serif; } .container { position: relative; width: 1200px; height: 630px; background-image: url("data:image/png;base64,#{base64_image}"); background-size: cover; background-position: center; display: flex; justify-content: center; align-items: center; } .text { color: #404040; font-size: 72px; font-weight: 700; text-align: center; width: 1050px; line-height: 1.3; word-wrap: break-word; } </style> </head> <body> <div class="container"> <div class="text">#{title}</div> </div> </body> </html> HTML html end
Puppeteer を呼べばOK
先ほどのHTMLを使うようにして、次のような感じでPuppeteerを呼べば output_path に画像が生成できました!
def self.generate(
title:,
image_path: Rails.root.join('lib/assets/og/poll_background.png').to_s,
output_path: Rails.root.join('tmp', 'out.png').to_s
)
html = make_html(title, image_path)
Puppeteer.launch(
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
) do |browser|
page = browser.new_page
page.set_content(html)
page.viewport = Puppeteer::Viewport.new(width: 1200, height: 630)
page.screenshot(path: output_path)
end
output_path
end
次のように絵文字入りのテキストも画像にできました🌸

まとめ
絵文字を使わなくて良いならば、MiniMagick の方がシンプルで良いと思います。しかし、絵文字を使うならば Puppeteer がめちゃくちゃ楽です。CSSを使うので表現力が高くなりますし、テキストの折り返しなども心配がなくなるメリットもあります。今回の調査で、フォント扱いの大変さと Puppeteer の便利さを実感しました。
おまけ
MeetGoGoでは、旅行に便利なツールを提供しています!ぜひご利用ください!!
みんなの日程調整: https://poppoline.com/meetgogo
みんなの割勘計算: https://poppoline.com/splits
