はじめに
React Router v7とCloudflare PagesでOGPタグの設定とOGP画像生成をやってみたいと思います。
OGPタグの設定
全体で共通の設定
アプリケーション全体で共通の設定をするには、root.tsxのレイアウトコンポーネントに定義します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | export function Layout({ children }: { children: React.ReactNode }) {   return (     <html lang="ja">       <head>         <meta charSet="utf-8" />         <meta name="viewport" content="width=device-width, initial-scale=1" />         {/* OGP:サイト名 */}         <meta property="og:site_name" content="プロフィール帳" />         {/* X向けの設定 */}         <meta property="twitter:card " content="summary_large_image" />         <meta property="twitter:site" content="@a_kaizaki" />         <Meta />         <Links />       </head>       <body>         {children}         <ScrollRestoration />         <Scripts />       </body>     </html>   ); } | 
meta()関数による動的な設定
React Router v7で、ルートモジュールごとに 
meta()
関数を定義することで、タイトルタグやメタタグを指定できます。ここで指定した内容は先ほどのレイアウトコンポーネントにある 
<Meta />
に入ります。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | import type { Route } from "../../.react-router/types/app/routes/+types/profile"; import { profiles } from "~/db"; export async function loader({ params }: Route.LoaderArgs) {   // meta・コンポーネントで使用するデータを取得   return profiles.find((profile) => profile.id === params.id); } export function meta({ data, location }: Route.MetaArgs) {   // loaderで取得したデータを元にmeta情報を生成   if (!data) {     return {       title: "Not Found",     };   }   const title = `${data.name}さんのプロフィール`;   const description = `${data.name}さんのプロフィールをチェックしよう`;   console.log("location.href", location);   return [     {       title,     },     { name: "description", content: description },     { property: "og:title", content: title },     { property: "og:description", content: description },     { property: "og:type", content: "profile" },     {       property: "og:url",       content: `https://example.pages.dev${location.pathname}`,     },   ]; } export default function Profile({ params, loaderData }: Route.ComponentProps) {   return (     <div>       <h1>プロフィール詳細</h1>       <p>ID: {params.id}</p>       {loaderData ? (         <>           <p>名前: {loaderData.name}</p>           <p>プロフィール: {loaderData.description}</p>         </>       ) : (         <p>Not Found</p>       )}     </div>   ); } | 
基本的には、Remix v2の時と似たような考え方ですが、React Router v7で導入された「型安全なルーティング」を活用しています。 
meta()
関数のPropsの型として使っているのは、このルートモジュールを元に自動生成された 
Route.MetaArgs
です。
 今回は、この中に含まれている 
data
を使っています。これは、 
loader()
の返り値の型になっています。
meta()
関数の戻り値は配列ですが、その1つひとつのオブジェクトは、以下のようなキーを指定することができます。
- title
 タイトルタグを指定するためのもの
- name
 メタタグのnameを指定するためのもの。(descriptionやkeywordなどを指定する)
- property
 メタタグのpropertyを指定するためのもの。主に、OGPのプロパティを指定するために使用する。
- content
 メタタグのcontentを指定するためのもの。
これで、以下のようなメタタグが出力されるようになりました。
| 1 2 3 4 5 6 7 8 9 10 11 | <meta charSet="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <meta property="og:site_name" content="プロフィール帳"/> <meta property="twitter:card " content="summary_large_image"/> <meta property="twitter:site" content="@a_kaizaki"/> <title>山田太郎さんのプロフィール</title> <meta name="description" content="山田太郎さんのプロフィールをチェックしよう"/> <meta property="og:title" content="山田太郎さんのプロフィール"/> <meta property="og:description" content="山田太郎さんのプロフィールをチェックしよう"/> <meta property="og:type" content="profile"/> <meta property="og:url" content="https://example.pages.dev/profiles/taro"/> | 
OGP画像の生成
ここからは、React Router v7でOGP画像を生成し、メタタグに挿入するところまでをやってみます。
 Remix時代のこちらの記事を参考にしながら、実装しました。
@vercel/ogの導入
OGPの生成には @vercel/og を使いますが、Cloudflare Pagesで動かすためにラッパーを使います。
| 1 | pnpm add @cloudflare/pages-plugin-vercel-og | 
middlwareの実装
OGPを生成するには、Cloudflare PagesのFunctions機能を使います。
 今回は、各ページごとに異なるOGPを生成するため、Cloudflare PagesのMiddleware機能を使います。
functions/profiles/_middleware.tsx
を作成します。
 このパスに作成することで、 
profiles/
配下にアクセスされた場合のみに実行することができます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | import vercelOGPagesPlugin from "@cloudflare/pages-plugin-vercel-og"; import { profiles } from "~/db"; export const onRequest = vercelOGPagesPlugin({   imagePathSuffix: "/og-image.png", // ファイル名を定義   component: ({ pathname }) => {     const paths = pathname.split("/");     // パスの最後の要素を取得     const slug = paths[paths.length - 1];     // データを取得     const profile = profiles.find((profile) => profile.id === slug);     return (       <div         style={{           display: "flex",           backgroundColor: "white",           width: "100%",           height: "100%",         }}       >         <div           style={{ width: "100px", height: "100%", backgroundColor: "#03a9f4" }}         />         <div           style={{             margin: "40px",             fontSize: "40px",           }}         >           {/* 1つのノードにまとめないとCloudflareではエラーが出る */}           {/* https://github.com/vercel/next.js/discussions/52657#discussioncomment-6442423 */}           {`@${slug} / ${profile?.name}`}         </div>         <div           style={{             position: "absolute",             left: "140px",             bottom: "40px",             fontSize: "30px",           }}         >           {profile?.description}         </div>       </div>     );   },   options: {     width: 1200,     height: 630,   },   autoInject: {     // メタタグを自動で挿入     openGraph: true,   }, }); | 
OGP画像のレイアウトは以前作ったものをそのまま使ってみました。
 実際に動作させるには、ローカル環境で 
pnpm run build && wrangler pages dev
を実行するか、実際にデプロイする必要があります。
これで、以下の画像が生成されました。
メタタグにも以下のように挿入されています。
| 1 2 3 | <meta property="og:image" content="http://localhost:8788/profiles/taro/og-image.png" /> <meta property="og:image:height" content="630" /> <meta property="og:image:width" content="1200" /> | 
OGPチェッカーで確認
最後に、実際にデプロイしてOGPチェッカーで内容を確認してみます。
問題なく認識されていますね!
さいごに
React Router v7関連の情報はまだ少ないので、これからも調査して情報発信していきたいと思います!
 
  
 
 
 

 
 
 
 
 
 
