Table of Contents
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返却- ブラウザからOGPプロキシAPIにリクエスト
- KVにキャッシュがあればそれを返却
- なければ対象サイトにfetchしてOGPを解析
- 結果を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
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
<title>タグ、最終的にはホスト名をフォールバックにしています。デプロイ
デプロイはwrangler deploy一発です。
wrangler deployカスタムドメインを設定したい場合は、Cloudflareダッシュボードの「Workers & Pages」→ 対象のWorker → 「Settings」→「Triggers」からカスタムドメインを追加できます。私はogp.p4ni.comで運用しています。
Astro側の連携
Astroのrehypeプラグインで、リンクカードコンポーネントからAPIを呼び出すようにしています。
// OGPプロキシAPIのベースURLconst OGP_API_BASE = "https://ogp.p4ni.com";
// コンポーネント内でfetchconst 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/