m13o

2023-05-12 Fri 21:36
Cloudflare Pages Functionsでogpの画像を生成するCloudflare

本サイトでは、サイトのmetaを整備する記事にあるように、imagemagickを使ってogpを生成していましたが、半年くらい前にCloudflare Pages Functionsのベータ版が終了し正式に一般リリースされたので、そちらを使って画像を自動生成するようにしました。

Cloudflare Pages Functionsはプラグインという形でミドルウェアを利用でき、またいくつかのミドルウェアはCloudflareによって既に用意されており、HTMLとCSSで記述されたモノをSSRしてくれる、vercel/ogがCloudflareによって用意されています。

Cloudflare Pages Functionsを作成する場合、functionsフォルダを用意し、その中にjavascriptもしくはtypescriptファイルを用意します。今回のようなミドルウェアを利用する場合は、ファイルのベース名を"_middleware"にする必要があります。

mkdir functions
touch functions/_middleware.tsx

そして、org-publish-project-alistにfunctionsディレクトリのパブリッシュ設定を追記します。

(custom-set-variables
 '(org-publish-project-alist
   `(("assets"
      ;; 省略
      )
     ("functions"
      :base-directory "./functions"
      :base-extension "js\\|ts\\|jsx\\|tsx"
      :publishing-directory "./publish/functions"
      :publishing-function org-publish-attachment
      :recursive t
      :author nil)
     ("posts"
      ;; 省略
      )
     ("tags"
      ;; 省略
      ))))

このサイトのデプロイ構造としては、publishディレクトリに出力する設定なので、functionsディレクトリもその中にパブリッシュする設定となっています。

パブリッシュ設定までできたので、実際にvercel/ogを利用してogp画像を生成します。とはいえ、基本はドキュメントにあるサンプルを踏襲すれば問題ありません。

import React from "react";
import vercelOGPagesPlugin from "@cloudflare/pages-plugin-vercel-og";

interface Props {
  ogTitle: string;
}

export const onRequest = vercelOGPagesPlugin<Props>({
  imagePathSuffix: "/social-image.png",
  component: ({ ogTitle, pathname }) => {
    return <div style={{ display: "flex" }}>{ogTitle}</div>;
  },
  extractors: {
    on: {
      'meta[property="og:title"]': (props) => ({
        element(element) {
          props.ogTitle = element.getAttribute("content");
        },
      }),
    },
  },
  autoInject: {
    openGraph: true,
  },
});

これは、ドキュメントに記載されているサンプルで、アクセスされたサイトのog:titleメタタグのcontent属性を取得し、それをcomponentに渡してSSRの元となるhtmlに加工するvercelOGPagesPluginをonRequestの実態にしています。このサンプルをそのまま利用すると、白背景に黒字のog:titleとして設定されている文字列が中央配置される、1200x630の大きさのsocial-image.pngになります。

vercelOGPagesPluginのimagePathSUffixは、functionsディレクトリをルートとした生成画像のファイルパスを表しています。実際の挙動としては、[サイトのルート]/[アクセスした記事][imagePathFuffix]のURLになります。サイトのルートが"https://example.com"、アクセスした記事が"og-image-site"の場合、生成された画像のURLは以下のようになります。

https://example.com/og-image-site/social-image.png

vercelOGPagesPluginのcomponentは、SSRに利用するReactコンポーネントで生成したい画像の構造をHTML+CSSで記述します。デフォルトのパラメータはpathnameのみですが、extractorsの内部で取得したデータも引数として追加できます。先の例では、ogTitleが新たに追加されています。

extractorsはonとonDocumentという2つのプロパティを持っており、それぞれ、要素解析とドキュメント解析に利用します。ドキュメント内の各要素に対して解析し、特定の文字列を取得する事が多いと思うので、基本的にはonの方を定義する事が多いです。onは、Cloudflare WorkesのRuntime APIにあるHTMLRewriterElementHandler型として内部的には定義されています。この中のelementを利用する事で、画像生成に必要な情報をドキュメントの各要素から取得します。なお、セレクタを利用したマッチングができるので、要素のしぼりこみも簡単です。

autoInjectはbool型で、これがtrueの場合、og:imageに関するメタ情報がサイトに自動的に付与されます。

なお、component内で画像の利用もでき、imgタグのsrc要素に直接URLを渡すか、dataURL形式を渡す事で、指定した画像が利用されます。普通のURLを渡す場合は相対パスだと上手く作用しない可能性があるのでフルパスを渡す方が良いでしょう。

これらの設定を施したfunctions/_middleware.tsxをパブリッシュすれば、ogp画像が自動で生成されるようになります。また、wranglerを使えばローカルでの検証も可能ですので、そちらで確認してからCloudflare Pagesにパブリッシュすると安全です。

注意として、ローカルで検証する分には問題ないのですが、Cloudflare Pagesにパブリッシュする時は、Cloudflare Pages Functionsの設定で、Compatibility flagsに"streams_enable_constructors"を追加しておかないと、うまく動いてくれません。また、Cloudflare Pages Functionsはその実態としてはCloudflare Workersなので、その利用には制限があります。