こんにちは、Webサイト作ってますか? Webサイトを作ってると、SNSやSlackにURLが貼られた時の見栄えを良くするために、OGP画像(og:image)を設定したくなりますよね。
筆者はCloudflare PagesとRemixでサイトを作ることが多くなってきたのですが、OGP画像を動的に生成する上手い方法を調べていたら一定の成果が出たので、共有しておこうと思います。
要点としては次のとおりです。
@vercel/og
ラッパーを使う.png
にする(Cloudflareのキャッシュを効かせるため)@vercel/og
OGP画像を動的に生成する分野には一定の需要がありますが、簡単に実現する方法というのはあまり多くありません。そんな中、Vercel社が提供している@vercel/og
というライブラリが一時期話題になりました。
https://vercel.com/docs/functions/og-image-generation
@vercel/og
は、HTMLとCSSで組まれたレイアウトを、PNG形式で出力するためのライブラリです。内部的には、HTML+CSSをSVGに変換するsatoriと、SVGをPNG等のラスター画像に変換するresvgで構成されています。
Vercel社が作っているライブラリということもあって、VercelプラットフォームのEdge Runtimeで使用される前提で作られています。エッジでOGP画像を動的生成して、そのままキャッシュすることで、コンピューティングリソースを効率的に使うことができるわけです。
では、Cloudflare Workersでも同様のことをしたい場合は、どうすればよいのでしょうか。先人たちが取り組んでくれていました。
https://zenn.dev/uzimaru0000/articles/satori-workers
https://speakerdeck.com/aiji42/cloudflare-workersdedong-kuoghua-xiang-sheng-cheng-qi
https://zenn.dev/beenos_tech/articles/20230413_generate-and-cache-ogp-image-on-cf
wasm版を使うのはなるほどなあとは思いつつ、satoriやresvgのwasm版を頑張って使う、という感じで、継続して使うには少しハードルが高いように感じました。
という情報を集めていたのが4月ごろだったのですが、6月になってから改めて調べたところ、Cloudflare Pages向けのラッパーを見つけました。
https://developers.cloudflare.com/pages/functions/plugins/vercel-og/
先行研究ではCloudflare Workersが使われていましたが、「あるWebページに対応した」「キャッシュされてほしい画像」を作るという目的を考えると、Cloudflare PagesのFunctionsで処理を行うのが適当ということなのでしょう。
これを使うと簡単に@vercel/og
を使うことができます。
もう紹介したいことの9割は紹介してしまったので、ドキュメントを読んで「はいはいそういうことね」と納得した方は時間がもったいないのでここで読み終わっておきましょう。残りは蛇足です。
というわけで、ここからはRemixでのちょっと実践的な使い方を紹介します。いうても/functions
フォルダの中でドキュメントの通りにライブラリを使うだけで、ちょっとだけRemixらしい味付けをする程度です。
まずは、Cloudflare Pages向けにセットアップされたRemixプロジェクトを作っておきましょう。
$npm create cloudflare@latest -- cloudflare-pages-vercel-og-remix-sample--framework=remix--deploy=false$cd cloudflare-pages-vercel-og-remix-sample
ついでにライブラリもインストールしておきます。
npminstall @cloudflare/pages-plugin-vercel-og
ヨシ!
@vercel/og
は非常に柔軟な表現ができるので、綺麗な背景を描画することも可能なのですが、どう考えても筆者のCSS力が足りないので、今回は背景に画像を貼り付けて豪華さを演出することにします。ChatGPTパイセンに「なんかいい感じのOGP用背景を作ってよ」とお願いしたら、こんな感じの画像を作ってくれました。
この背景の上に、別のアイコンやらテキストやらを重ねていく感じで進めてみましょう。
というわけで、適当にpublic/img/ogp-background.png
というパスで保存しておきます。これはデプロイ後に/img/ogp-background.png
としてアクセスできるようになることを期待して配置します。
これで下準備ができました。
今度は実際にOGP画像を生成する関数を作っていきましょう。app/routes/articles.$slug.tsx
に定義したページ(つまり/articles/:slug
で表示されるページ)のURLを貼ったときに出てくるOGP画像を生成したいと思います。
/articles
以下のページにアクセスされたときのリクエストやレスポンスに介入する処理なので、Middleware機能を利用するために/functions/articles/_middleware.tsx
を作成します。
importvercelOGPagesPluginfrom"@cloudflare/pages-plugin-vercel-og";exportconst onRequest=vercelOGPagesPlugin({ imagePathSuffix:"/og-image.png",// ファイル名を定義component:({ pathname})=>{// pathnameから最後の要素を抜き出すconst paths= pathname.split("/");const slug= paths[paths.length-1];return(<divstyle={{// flexboxで中央寄せ display:"flex", flexDirection:"column", alignItems:"center", justifyContent:"center",// 画像の幅いっぱいまでdivを広げる width:"100%", height:"100%",}}><h1style={{ fontSize:"80px"}}> This is{slug} article</h1></div>);}, options:{// 画像のサイズを指定 width:1200, height:630,}, autoInject:{// ページのhead要素内ににOGの<meta>要素を追加 openGraph:true,},});
URLのパスからslugを取り出して、それを使ってcomponent
プロパティにJSXでレイアウトを組んでいます。画像の中央にテキストを表示するだけのシンプルなものです。パラメータ自体はほとんどsatoriと共通なので、特には説明しません。
imagePathSuffix
は、元のURLに対応するOGP画像がどのパスに保存されるかを指定します。今回は、ページURLの末尾にog-image.png
がついたパスに保存されるようにしていますので、/articles/hogehoge
にアクセスされたときには/articles/hogehoge/og-image.png
のパスでOGP画像が取得できるようになります。
実際にCloudflare Pageにデプロイして、取得した画像は次のような感じになります。
背景が透過しているので、このままだと環境によっては見づらいかもしれません。
見栄えを良くするために、先ほど作った背景画像もセットしてみましょう。
// (略) return ( <div style={{ // flexboxで中央寄せ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", // 画像の幅いっぱいまでdivを広げる width: "100%", height: "100%", // 背景画像を設定+ backgroundImage: 'url("https://cloudflare-pages-vercel-og-remix-sample.pages.dev/img/ogp-background.png")', }} >- <h1 style={{ fontSize: "80px" }}>+ <h1 style={{ fontSize: "30px" }}> This is {slug} article </h1> </div> ); // (略)
backgroundImage
はフルパスで指定するのがコツです。上手く背景に収めるために、テキストのフォントサイズは小さくしておきました。
これをデプロイして、/articles/hogehoge/og-image.png
にアクセスすると、次のような画像が取得できます。
上手いこと背景画像が表示されました。凝った背景は事前にラスター画像として用意しておくことで、少ない労力で見栄えを良くすることができます。
前述の通り、autoInject.openGraph = true
が設定されているので、HTMLには自動で設定されます。/articles/hogehoge
にアクセスした場合のHTMLを確認してみましょう。
<!DOCTYPEhtml><htmllang="en"><head><metacharSet="utf-8"/><metaname="viewport"content="width=device-width, initial-scale=1"/><linkrel="stylesheet"href="/assets/root-BFUH26ow.css"/><metaproperty="og:image"content="https://cloudflare-pages-vercel-og-remix-sample.pages.dev/articles/hogehoge//og-image.png"/><metaproperty="og:image:height"content="630"/><metaproperty="og:image:width"content="1200"/></head><!-- 略 -->
meta要素が自動で挿入されています。便利ですね。
@cloudflare/pages-plugin-vercel-og
のデフォルトの関数を使った場合は、middleware機能を前提としたAPIになっていますが、もちろん任意のパスでOGP画像を生成することもできます。
import{ImageResponse}from"@cloudflare/pages-plugin-vercel-og/api";exportconst onRequest:PagesFunction=async()=>{returnnewImageResponse(<divstyle={{ display:"flex"}}>Hello, world!</div>,{ width:1200, height:630,});};
こちらの方法のほうが、@vercel/ogのサンプルに近い形ですね。
この方法で作成した場合は、/greet
にアクセスすることでOGP画像を取得できます。通常のFunctionsのルーティングに従ってパラメータを取得できるので、ぜひ活用してみてください。
注意点として、公式サンプルでは/greet
というパスで画像を返していますが、デフォルトではこのパスにはキャッシュが効きません。キャッシュが効くように明示的に設定することも可能だとは思いますが、Cloudflareのデフォルトのキャッシュ設定に含まれるようにパス名を変更した方が簡単です。コード側のファイル名を/functions/greet.png.tsx
に変更しておくと、アクセス時のパスが/greet.png
になるので、キャッシュが効くようになります。
というわけで、今回のソースコードはこちらにまとめてあります。
https://github.com/Nkzn/cloudflare-pages-vercel-og-remix-sample
実際に動作している様子はこちら。
https://cloudflare-pages-vercel-og-remix-sample.pages.dev/articles/hogehoge/
Cloudflare Pages環境でも簡単に@vercel/og
を使ってOGP画像を生成する方法を紹介しました。ルーティングや画像置き場についてはRemixに準拠して解説しましたが、Hono等でも同様の手順で実施できるはずです。
株式会社モニクルは、「金融の力で、安心を届ける。」をミッションとする金融サービステック企業です。
もしこの記事を読んで会社にも興味を持っていただけたら、↓ の Culture Deck(会社説明資料)を読んでみてください。
https://speakerdeck.com/moniclegroup/culture-deck
エンジニア採用サイトはこちら
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。