👷

Cloudflare PagesでSPAのOGP対応をする

Hidetoshi Ota2022/10/03に公開

ファンタラクティブのエンジニアの 太田 です。
最近はフロントに関する技術、サービスとしてCDN Edgeに注目しています。
今回はCloudflare PagesのFunctions (beta)を使用して、SPAのOGP対応ができないか試してみたので紹介します。
なかなか仕事で使用する機会がまだないため少しずつ手探りで試している状況ですので、間違っていたら教えていただけると助かります。

前提

CSR (SSRやSSGを使用しない) で動的ルーティンに対するOGP対応することは基本的にはできない。

https://zenn.dev/funteractive/articles/spa-build-diff

想定

既存のCSRで作られたシステムが存在する。
基本的にログインして使用するサイトなので、ログイン後のページのOGPは必要ないはずだったが、特定の動的ルーティンページだけSNSでシェアする要件を追加したくなった。
そのページは不特定多数のユーザーが作成できる。
なんらかの理由でSSRやOn-demand ISRを使用できない (使用したくない)。

Cloudflare Pagesを使って対応する


処理の流れ

上の図のようにページへのリクエストをFunctionsでインターセプトし、静的なHTMLとOGPに必要なデータを取得し、HTMLを加工して返します。

実装

ディレクトリ構成

Next.jsでexportした感じのディレクトリを想定して書いています。
outディレクトリはビルド後の静的ファイルを配置し、Cloudflare Pagesにホスティングします。
functionsディレクトリにはworkerの処理を書きデプロイします。

.
├── functions
│   └── characters
│       └── [id]
│           └── index.ts
└── out
    └── characters
        └── [id]
            └── index.tsx

Functions

次に実際にFunctionsの実装をします。
HTMLRewriterクラスを使用し、HTMLを書き換えて返します。

type Character = {
  id: number
  name: string
  image: string
}

const ROUTES = {
  characterDetail: { static: 'characters/[id]' }
}

const htmlRewriter = ({ character }: { character: Character }) =>
  new HTMLRewriter().on('head', {
    element: (e) => {
      // head内を書き換え
      e.append(`<title>${character.name}</title>`, { html: true })
      e.append(`<meta property="og:title" content="${character.name}" />`, { html: true })
      e.append(`<meta property="og:description" content="${character.name}のページです" />`, { html: true })
      e.append(`<meta property="og:image" content="${character.image}" />`, { html: true })
    },
  })

export const fetchCharacter = (id: number): Promise<Character> =>
  fetch(`https://hoge/api/character/${id}`).then((x) => x.json())

export const onRequestGet: PagesFunction = async ({ request, params, env }) => {
  // OGPに必要なデータと静的なページを取得
  const [character, res] = await Promise.all([
    fetchCharacter(params.id as any as number),
    env.ASSETS.fetch(
      new Request(new URL(ROUTES.characterDetail.static, request.url).toString()),
      request
    ),
  ])

  return htmlRewriter({ character }).transform(res)
}

/characters/xxx にアクセスすると各ページ毎に異なるOGPが表示されるようになります。

あとがき

実際に紹介した方法でOGP対応するケースは少ないと思います。
ただCSRで運用中のシステムをSSRせずにOGPに対応できるのでいざというとき役立つのではと思いました。(Cloudflareにロックインされるけど)
VercelなどCloudflare以外でも似たようなサービスが登場しているのを見ると、改めてここが熱い分野なんだろうと感じています。
Firebase HostingでFunctionsにrewriteとも少し似てると感じましたが、CDNで実行している点でFirebaseとは異なりCloudflareが有利に感じました。
あまり触れていませんが、Cloudflareは他にも魅力的なサービスがあるようにみえるので積極的に触っていきたいと思っています。

Discussion

ログインするとコメントできます