[前提]この記事は90%AIによって生成されました。
このメモは、ro1.dev で運用している OGP API 実装を、別サービスへ横展開するための実装仕様書です。
「LLM にこの文書を渡せば、最小の補足で実装できる」粒度でまとめています。
##結論
このブログでは OGP を次の2系統で実装しています。
- 動的生成系:
pages/apiで JSX -> SVG -> PNG を生成して返す - プリビルド配信系: ビルド時に PNG を大量生成し、API は
302 redirectだけ返す
この分離が効いた理由は明確で、動的生成の柔軟性を残しつつ、Cloudflare Workers のサイズ・ランタイム制約を回避できるからです。
##1. 対象アーキテクチャ
###1-1. 実行基盤
- Next.js 15 (Pages Router)
- API:
src/pages/api/** - デプロイ: OpenNext + Cloudflare Workers
- Workers 設定:
wrangler.jsonc
wrangler.jsonc の要点:
{
"main": ".open-next/worker.js",
"minify": true,
"compatibility_flags": ["nodejs_compat"],
"images": {
"binding": "IMAGES"
},
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS"
}
}
IMAGES binding は、Workers ランタイムで SVG -> PNG 変換するために必須です。
###1-2. ビルド導線
package.json
{
"scripts": {
"sync:content-snapshots": "node scripts/generate-content-snapshots.mjs",
"generate:ogp-prebuilt": "node scripts/generate-ogp-prebuilt-assets.mjs",
"prebuild": "bun run sync:content-snapshots && bun run generate:ogp-prebuilt",
"build:cf": "opennextjs-cloudflare build"
}
}
ポイントは prebuild で OGP 静的アセットも更新することです。
##2. OGP 実装のコア
共通実装は src/lib/og/image-response.tsx に集約しています。
- 責務
satoriで JSX から SVG を生成- Node 実行時:
@resvg/resvg-wasmで PNG 化 - Workers 実行時:
env.IMAGESで PNG 化 - フォントをローカル優先で読み込み(
public/fonts-> remote fallback)
実質ここが「実行環境差分の吸収層」です。
###最小形
export const createImageResponse = async (element: ReactElement, options = {}) => {
const svg = await satori(element, { width, height, fonts });
const png = await renderPngFromSvg(svg, fonts); // Node: resvg / Workers: IMAGES
return new Response(png, {
status: 200,
headers: { "content-type": "image/png" },
});
};
##3. 系統A: 動的生成系 API
クエリで都度画像を作る
-
実例
src/pages/api/app/ogp.tsxsrc/pages/api/og/index.tsx
-
実装パターン
runtime: "nodejs"を宣言- クエリを正規化(長さ制限、空文字処理)
- JSX レイアウトを
createImageResponseに渡す - 例外時は 500 を返す
###雛形
export const config = { runtime: "nodejs" };
export default async function handler(req: NextApiRequest) {
const title = typeof req.query.title === "string"
? req.query.title.slice(0, 100)
: "Default Title";
return await createImageResponse(<Card title={title} />, {
width: 1200,
height: 630,
});
}
この系統は「ユーザー入力で即時に OGP を変えるサービス」に向いています。
##4. 系統B: プリビルド配信系 API(大量記事向け)
-
実例
src/pages/api/memo/ogp.tssrc/pages/api/ro1dev/ogp.tsx
-
設計
- ビルド時に OGP PNG を
public/ogp-api/**へ生成 - 同時に manifest JSON を
src/lib/og/generated/*.jsonへ生成 - API route はクエリをキー化して manifest を引く
- 見つかれば
302 redirect、なければ default 画像へ redirect
- ビルド時に OGP PNG を
###memo API の動作仕様
- endpoint:
/api/memo/ogp - query:
page=top | posttitle(post時のみ)date(post時のみ)
- response:
302+/ogp-api/memo/...pngCache-Control: public, max-age=0, s-maxage=86400, stale-while-revalidate=604800
###ro1dev API の動作仕様
- endpoint:
/api/ro1dev/ogp - query:
title - response
- 上と同様に
302
- 上と同様に
##5. キー生成ルール
src/lib/og/prebuilt-ogp-key.ts のルールを API 側と生成側で一致させています。
- 空白正規化:
trim + 連続空白を1個 - 長さ制限:
- memo title: 120
- memo date: 40
- ro1dev title: 100
- memo key:
title + "\u0001" + date
これがズレると「manifest にあるのにヒットしない」事故になります。
##6. 生成バッチ仕様(scripts/generate-ogp-prebuilt-assets.mjs)
-
やっていること
- snapshot JSON から全記事データを読み込む
- SVG テンプレートにタイトル/日付を流し込む
sharpで PNG に書き出す- ハッシュ付きパスを発行して manifest を作る
memo-ogp-manifest.json/ro1dev-ogp-manifest.jsonを更新
-
この方式の利点
- API 実行時に重い画像生成が不要
- Workers script サイズ増加を抑えられる
- キャッシュ制御が単純になる
##7. ページ側との接続
ページコンポーネントは API URL を直接組み立てず、専用 helper を使っています。
src/lib/memo/ogp.tssrc/lib/og/ro1dev.ts
###例
const ogImage = buildMemoPostOgpUrl({ title: post.title, date: post.date });
この helper 層を置くと、将来 endpoint やキー制約を変えてもページ側の変更を局所化できます。
##8. 障害パターンと対処
###よく出る障害
env.IMAGES binding is not definedrender2 is not a function/OGImageResponse is not a constructor- Worker size 上限超過(3MiB制限)
- 本番だけ
fs読み込み失敗
###対処
wrangler.jsoncへimages.binding=IMAGESを明示- OGP を共通
createImageResponse経由に統一 - プリビルド画像 + redirect 方式へ寄せる
- コンテンツは snapshot JSON 化して
prebuildで生成
##9. 横展開テンプレート
新規サービスへ移植する場合は以下をそのまま適用できます。
src/lib/og/image-response.tsx相当を作成- 動的系 endpoint を1本作る(まずは最低限)
- 記事/投稿が多い領域だけプリビルド系 endpoint を追加
scripts/generate-ogp-prebuilt-assets.mjsをサービス用テンプレへ複製prebuildに snapshot + prebuilt 生成を組み込む- OGP URL builder helper を用意してページから利用
##10. LLMへ渡す実装プロンプト
以下をそのまま指示すれば、別プロダクトでも再現しやすいです。
Next.js + Cloudflare Workers で OGP API を実装してください。
要件:
- API Routes 配下に OGP endpoint を作成
- runtime は nodejs を使用
- JSX -> SVG は satori、PNG化は
- Node: @resvg/resvg-wasm
- Workers: Cloudflare IMAGES binding
- createImageResponse ユーティリティに環境差分を集約
- 大量コンテンツ向けに prebuilt OGP 方式を実装
- build 時に PNG を public/ogp-api/** へ生成
- manifest JSON を src/lib/og/generated/** に生成
- API は manifest lookup + 302 redirect
- title/date の正規化と最大長を API と生成側で共通化
- page 側は helper で OGP URL を組み立てる
- Cache-Control は s-maxage + stale-while-revalidate を設定
成果物:
- 共通OGPレンダラ
- 動的OGP API 1本
- prebuilt OGP API 1本
- prebuild script
- OGP URL helper
- wrangler 設定(IMAGES binding 含む)
このテンプレをベースに、サービス固有のレイアウト(色・ロゴ・文言)だけ差し替えれば実戦投入できます。