この記事の要点
- 記事のアイキャッチ(OGP画像)を、Cloudflare Workers AIのflux-1-schnellで自動生成する構成を実装した。
- 画像モデルが不安定・混雑でも公開を止めないため、決定論的なSVGフォールバックを必ず用意する。
- Cloudflareの無料枠のみ(Workers/Pages/R2/Workers AIの無料分)で回しており、追加費用は0円。実運用のコードを交えて解説する。
なぜアイキャッチを自動生成するのか?
コンテンツを自動生成するパイプラインを組むと、最後に必ず「アイキャッチ画像をどうするか」が残ります。人手で毎回作るとそこが律速になり、「全自動」が崩れます。かといって有料の画像APIを常用すると、記事1本あたりのコストが積み上がります。
そこで、Cloudflare Workers AIの画像生成モデルを使い、記事タイトルからアイキャッチを自動生成する構成にしました。ポイントは「生成に失敗しても公開は止めない」ことです。
使うモデルと料金の考え方
Workers AIは無料枠(1日あたりのNeuron上限)内なら追加課金なしで利用できる。上限を超えた分だけ従量課金になる。
画像は @cf/black-forest-labs/flux-1-schnell を使います。schnell(=ドイツ語で「速い」)の名の通り数ステップで生成でき、レスポンスが速くバッチ向きです。生成結果はbase64のPNGで返るので、そのままR2に保存してOGPのURLにします。
実装:生成 → 失敗時フォールバック
肝は try/catchで画像生成を包み、失敗したら例外を投げずにSVGを返す ことです。公開パイプラインから安全に呼べます。
export interface OgpInput {
title: string
category?: string
slug: string
}
// Workers AI で PNG を生成。失敗したら null を返す(例外を投げない)
async function generateFluxPng(env: Env, prompt: string): Promise<ArrayBuffer | null> {
try {
const res: any = await env.AI.run('@cf/black-forest-labs/flux-1-schnell', {
prompt,
steps: 4,
})
// flux は { image: base64 } を返す
const b64 = res?.image
if (!b64) return null
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)).buffer
} catch (e) {
console.warn('flux failed, will fall back to SVG', e)
return null
}
}
フォールバックのSVGは外部リソースに一切依存しない(フォントも色もインラインで完結する)ように書きます。これで「必ず何かは出る」を保証できます。
function fallbackSvg(d: OgpInput): string {
const title = escapeXml(d.title).slice(0, 40)
const cat = escapeXml(d.category || '宅建コラム')
return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#0b1e3f"/>
<stop offset="1" stop-color="#123a6b"/>
</linearGradient>
</defs>
<rect width="1200" height="630" fill="url(#g)"/>
<text x="80" y="150" fill="#7fd4ff" font-size="34" font-family="sans-serif">${cat}</text>
<text x="80" y="330" fill="#ffffff" font-size="64" font-weight="bold"
font-family="sans-serif">${title}</text>
</svg>`
}
function escapeXml(s: string): string {
return s.replace(/[<>&"']/g, (c) =>
({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[c] as string),
)
}
呼び出し側は「PNGが取れればPNG、ダメならSVG」を選ぶだけです。
export async function buildOgp(env: Env, d: OgpInput): Promise<{ body: BodyInit; contentType: string }> {
const prompt = `A clean, professional cover image for an article titled "${d.title}". Minimal, blue tones, no text.`
const png = await generateFluxPng(env, prompt)
if (png) return { body: png, contentType: 'image/png' }
return { body: fallbackSvg(d), contentType: 'image/svg+xml' }
}
よくある失敗
- 画像生成の失敗で公開が止まる:モデルは混雑や一時エラーで落ちることがある。生成は「あれば嬉しい」程度に扱い、フォールバックを本線として設計する。
- フォールバックが外部依存:Webフォントや外部画像を参照するSVGにすると、そこが落ちたときに二重で失敗する。色もフォントもインラインで完結させる。
-
base64のデコードミス:
atobの結果は文字列なので、Uint8Array.from(..., c => c.charCodeAt(0))でバイト列に戻す。ここを飛ばすと画像が壊れる。 - プロンプトに文字を描かせる:画像モデルは日本語テキストの描画が苦手。**「no text」**を明示し、タイトル文字はSVG側で載せると破綻しない。
まとめ
- アイキャッチ自動生成は「生成できたら使う」「失敗したら決定論的SVG」の二段構えにすると、全自動パイプラインが止まらない。
- Cloudflare Workers AIの無料枠で回せば、記事量産のコストは実質0円に抑えられる。
- 実運用では宅建学習アプリ「宅建BOOST」のコラム自動生成に組み込み、毎時の予約公開まで無人で回しています。
参考: 宅建BOOST コラム(この構成で生成したアイキャッチを使っています)
#Cloudflare #TypeScript #生成AI #OGP
公式サイト: https://my-agent.work/