こちらの記事を読んで。
以前から https://reading-list.zaki-yama.dev/ という自分のサイトに OG 画像を設定したいな〜と思っていたところに @vercel/og なるライブラリが登場したので試してみた。
※ なお、今までずっと「OGP 画像」だと思ってたんだけど、記事中では OG image と表記されていたので、ここでも「OG 画像」と表記する。
@vercel/og の特徴
冒頭の記事を読む限り、今までは vercel/og-image というサービスが提供されていたが、これには以下のような問題があった。
- 難しい: Serverless Function 内で Chromium を起動し、 Puppeteer でスクリーンショットを撮る、ということをやっていた。これらのツールのセットアップは実装が難しくしばしばエラーを引き起こしていた
- 遅い:Chromium を Serverless Function 内で起動するため圧縮し、コールドブート中に解凍される必要があるため、遅い(平均4秒)。結果、ソーシャルカードの生成が遅くなったり壊れたりした
今回発表された @vercel/og はこれらの問題を解決したもので、具体的には以下の特徴がある。
- 簡単 (Easy):ヘッドレスブラウザ不要。HTML と CSS から OG 画像を生成できる
- 高速 (Fast): vercel.com/docs で試した結果 vercel/og-image より 5倍程度速かったとのこと
加えて @vercel/og を利用したコードは Next.js アプリケーションと共存でき、 vercel/og-image のように別のところにデプロイする必要がない というのもうれしい。
@vercel/og が使える条件
https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation#requirements を読む限り
- Next.js のバージョンが v12.2.3 以降
runtime: 'experimental-edge'
を有効にして Edge Runtime を使用する
を満たす必要がある。後者はデプロイ先が Vercel であれば問題にならないが、他だとどうなんだろ。やったことないのでわからない。
試してみる
というわけで自分で運用している Next.js アプリケーションの https://reading-list.zaki-yama.dev に @vercel/og を導入してみる。
ソースコードは https://github.com/zaki-yama/reading-list
公式ドキュメントとしては以下を読めばよさそう。
簡単な OG 画像を表示する
まずは、ライブラリを導入して静的な OG 画像を表示する。
インストールは以下のコマンドで行う。
$ npm i @vercel/og
続いて、OG 画像生成用の API エンドポイントを用意する。
/pages/api/og.tsx
というファイルを作る。
// /pages/api/og.tsx import { ImageResponse } from '@vercel/og'; export const config = { runtime: 'experimental-edge', }; export default function () { return new ImageResponse( ( <div style={{ fontSize: 128, background: 'white', width: '100%', height: '100%', display: 'flex', textAlign: 'center', alignItems: 'center', justifyContent: 'center', }} > Hello world! </div> ), { width: 1200, height: 600, }, ); }
(コードは https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation#getting-started を引用)
これで npm run dev
して http://localhost:3000/api/og にアクセスすると Hello world! という画像が表示される。
最後に、これが OG 画像として表示されるようにする。それには <head>
に以下のように meta タグを埋め込めばよい。
(自分は Layout コンポーネント内に定義した)
<meta property="og:image" content="https://reading-list.zaki-yama.dev/api/og" />
テキストを動的に設定する
基本的な使い方がわかったので、次は画像内のテキストを動的に設定してみる。
具体的には、現在開いているページのタイトルを OG 画像に埋め込むようにしたい。
OG Image Examples > Dynamic text generated as image にちゃんとサンプルが用意してある。
まず、 /pages/api/og.tsx 側は以下のように、クエリパラメータでタイトルを受け取るようにする。
// /pages/api/og.tsx import { ImageResponse } from '@vercel/og'; import { NextRequest } from 'next/server'; export const config = { runtime: 'experimental-edge', }; export default function handler(req: NextRequest) { try { const { searchParams } = new URL(req.url); // ?title=<title> const hasTitle = searchParams.has('title'); const title = hasTitle ? searchParams.get('title')?.slice(0, 100) : 'My default title'; return new ImageResponse( ( <div> // ...略... {title} </div> ), { width: 1200, height: 630, }, ); } catch (e: any) { console.log(`${e.message}`); return new Response(`Failed to generate the image`, { status: 500, }); } }
(コードは https://vercel.com/docs/concepts/functions/edge-functions/og-image-examples#dynamic-text-generated-as-image から抜粋)
そして、使う側は
<meta property="og:image" content="https://reading-list.zaki-yama.dev/api/og?title=${postData.title}" />
というようにクエリパラメータをページごとに変えて設定してやる。
1点サンプルには載ってなかったこととして、自分は以下のように URLSearchParams
をかませるようにした。
// /pages/posts/[id].tsx export default function Post({ postData, }: { postData: { title: string; date: string; contentHtml: string; }; }) { const title = `${siteTitle} ${postData.title}`; const searchParams = new URLSearchParams(`title=${title}`); return ( <Layout> <Head> <title>{title}</title> <meta property="og:image" content={`https://reading-list.zaki-yama.dev/api/og?${searchParams.toString()}`} key="og-image" /> <meta property="og:title" content={postData.title} key="og-title" /> </Head> ...
(https://github.com/zaki-yama/reading-list/blob/main/pages/posts/%5Bid%5D.tsx#L17-L27)
おそらくサイトや記事のタイトルに '
や (スペース)が含まれていたせいで
Open Graph Debugger で見たときにうまく表示されなかったため、これらの文字は URL エンコードするようにした。
結果
https://reading-list.zaki-yama.dev/posts/2022-09-13 という URL を Twitter や Slack に貼ったときに OG 画像が表示されるようになった。
デザイン性皆無なのはしょうがないが、一旦満足。