OGPプロキシをCloudflare Workersで自作してみた

ブログにリンクカードを表示するためにこれまで外部API(microlink.io)を使っていたのですが、外部サービスへの依存を減らしたかったので、Cloudflare Workersで自前のOGPプロキシを作ってみました。


なぜ自作したのか#

  • 外部APIへの依存を減らしたい - microlink.ioは便利ですが、サービスの継続性やレート制限が気になっていました
  • Cloudflare Workersの無料枠で十分 - 1日10万リクエストまで無料なので、個人ブログには余裕すぎます
  • KVキャッシュで高速化 - 一度取得したOGP情報は24時間キャッシュするので、同じURLへの再リクエストは爆速です

アーキテクチャ#

シンプルな構成です。

ブラウザ → Cloudflare Workers → 対象サイト
KVキャッシュ(24時間)
OGP情報をJSON返却
  1. ブラウザからOGPプロキシAPIにリクエスト
  2. KVにキャッシュがあればそれを返却
  3. なければ対象サイトにfetchしてOGPを解析
  4. 結果をKVに保存してJSON返却

実装#

Workers + KVの設定#

wrangler.tomlでKV Namespaceをバインドします。

name = "ogp-proxy"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# KV Namespace binding
[[kv_namespaces]]
binding = "OGP_CACHE"
id = "your-kv-namespace-id"
[vars]
ALLOWED_ORIGINS = "https://your-site.com,http://localhost:4321"
CACHE_TTL_SECONDS = "86400" # 24時間

KV Namespaceはwrangler kv namespace create OGP_CACHEで事前に作成しておきます。

メインのfetchハンドラ#

リクエストを受けてOGP情報を返す部分です。

export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// CORS preflight
if (request.method === "OPTIONS") {
return handleCORS(request, env);
}
// /ogp エンドポイントのみ処理
if (url.pathname !== "/ogp") {
return new Response("Not Found", { status: 404 });
}
const targetUrl = url.searchParams.get("url");
if (!targetUrl) {
return jsonResponse({ error: "url parameter is required" }, 400, request, env);
}
try {
// キャッシュ確認
const cacheKey = `ogp:${targetUrl}`;
const cached = await env.OGP_CACHE.get<CacheEntry>(cacheKey, "json");
const ttl = parseInt(env.CACHE_TTL_SECONDS, 10) || 86400;
if (cached && Date.now() - cached.timestamp < ttl * 1000) {
return jsonResponse(cached.data, 200, request, env);
}
// OGP情報を取得
const ogpData = await fetchOGP(targetUrl);
// キャッシュに保存
await env.OGP_CACHE.put(cacheKey, JSON.stringify({
data: ogpData,
timestamp: Date.now(),
}), { expirationTtl: ttl });
return jsonResponse(ogpData, 200, request, env);
} catch (error) {
return jsonResponse({ error: "Failed to fetch OGP data" }, 500, request, env);
}
},
};

OGP取得ロジック#

対象サイトにfetchしてHTMLを取得し、正規表現でOGPメタタグを解析します。

async function fetchOGP(targetUrl: string): Promise<OGPData> {
const response = await fetch(targetUrl, {
headers: {
"User-Agent": "Mozilla/5.0 (compatible; OGPBot/1.0; +https://p4ni.com)",
Accept: "text/html,application/xhtml+xml",
"Accept-Language": "ja,en;q=0.9",
},
redirect: "follow",
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const html = await response.text();
return parseOGP(html, targetUrl);
}

User-Agentはちゃんとボットだと明示しておくのが礼儀かなと思います。

HTMLパース(正規表現でmetaタグ抽出)#

正規表現でog、og、ogなどを取得します。DOMパーサーを使わずに正規表現でやっているのは、Workersの軽量さを維持するためです。

function parseOGP(html: string, targetUrl: string): OGPData {
const url = new URL(targetUrl);
const getMetaContent = (patterns: RegExp[]): string => {
for (const pattern of patterns) {
const match = html.match(pattern);
if (match?.[1]) {
return decodeHTMLEntities(match[1].trim());
}
}
return "";
};
// title: og:title → twitter:title → <title>タグの順でフォールバック
const title = getMetaContent([
/<meta[^>]+property=["']og:title["'][^>]+content=["']([^"']+)["']/i,
/<meta[^>]+name=["']twitter:title["'][^>]+content=["']([^"']+)["']/i,
/<title[^>]*>([^<]+)<\/title>/i,
]) || url.hostname;
// description, image, favicon, siteName も同様にパース...
return { title, description, image, favicon, siteName };
}

ogがなければtwitter、それもなければ<title>タグ、最終的にはホスト名をフォールバックにしています。


デプロイ#

デプロイはwrangler deploy一発です。

Terminal window
wrangler deploy

カスタムドメインを設定したい場合は、Cloudflareダッシュボードの「Workers & Pages」→ 対象のWorker → 「Settings」→「Triggers」からカスタムドメインを追加できます。私はogp.p4ni.comで運用しています。


Astro側の連携#

Astroのrehypeプラグインで、リンクカードコンポーネントからAPIを呼び出すようにしています。

// OGPプロキシAPIのベースURL
const OGP_API_BASE = "https://ogp.p4ni.com";
// コンポーネント内でfetch
const apiUrl = `${OGP_API_BASE}/ogp?url=${encodeURIComponent(url)}`;
fetch(apiUrl)
.then(response => response.json())
.then(data => {
// title, description, image, favicon, siteNameを表示
});

Markdown側では::linkcard{url="https://example.com"}という記法でリンクカードを埋め込めるようにしています。


まとめ#

  • 外部サービスに依存せず、自前でOGP取得→リンクカード表示ができるようになりました
  • Cloudflare Workersの無料枠(10万req/日)で十分運用可能
  • KVキャッシュで同一URLへの再リクエストは高速
  • 正規表現パースなのでエッジケースはあるかもしれませんが、個人ブログ用途では十分です

参考#

OGPプロキシをCloudflare Workersで自作してみた
https://p4ni.com/posts/cloudflare-workers-ogp-proxy/
作者
kpab
公開日
2026-01-21
ライセンス
CC BY-NC-SA 4.0