見出し画像が無い記事を「公開させない」のをやめた話 — ゲートを足すと詰まる
この記事について
版元が運用している媒体のひとつ(以下 TB)で自動公開を回していたところ、見出し画像が無いまま記事が1本公開された(2026-07-23)。素直な対処は「画像が無い記事は公開させない」というゲートを1行足すことだが、実際にはそれを避けた。この記事は、TB でなぜゲートを足さなかったか、代わりに何を作ったかの記録。
zashstudio 自身の公開ループは別構成で、見出し画像が無ければフォールバックを返さず null を返す作りになっている。以下は TB で採った設計の解説であって、この媒体の実装そのものではない。
何が起きたか
TB のように複数の記事を時間差で自動公開している仕組みでは、起稿する側と画像を用意する側が別工程になっている。起稿側は画像生成の完了を待たずに原稿を出すことがあり、そのまま公開スロットに乗ると画像なしの記事が本番に出る。
これ自体は珍しい事故ではない。問題は対処の選び方だった。
ゲートを入れなかった理由
「featuredImage が空なら公開しない」というチェックは実装コストが低く、一見すると正しい。しかし在庫を先行して積む運用と相性が悪い。
| 選択肢 | 何が起きるか |
|---|---|
| 公開前ゲートを足す | 画像が遅れている記事が公開キューに詰まる。詰まった記事の後ろに並ぶ健全な記事まで止まる |
| ゲートを足さない・後から追いつく | 画像なしで公開される瞬間はあるが、供給は止まらない。後続の処理が拾って埋める |
在庫が薄い状態でゲートを入れると、「公開できるはずだった記事」まで巻き込んで公開レーン全体が弾切れする。1本の画像待ちが全体のスループットを止めるのは、この構成では割に合わないと判断した。
採った設計:受け身と能動の2段
止めるのではなく、公開は流したまま2段で埋める形にした。
受け身の担保: 記事に見出し画像が無い場合、描画するときにだけ共通のフォールバック画像を当てる。og:image・twitter:image・構造化データの image は必ず何かを指す状態にする。
export const DEFAULT_OG_IMAGE = '/img/default-og.webp';
export function resolveImage(featuredImage) {
return featuredImage && featuredImage.trim() !== ''
? featuredImage
: DEFAULT_OG_IMAGE;
}
能動の担保: 見出し画像が無い記事を定期的に拾って生成し、frontmatter に差し込む常駐処理を別に回す。公開前の在庫を優先して埋めるので、実際に画像なしで公開される窓は縮む。
設計の急所は「フォールバックを永続化しないこと」
ここで一番効いた判断は、共通サムネを frontmatter に書き込まないことだった。
フォールバックは描画のたびに計算するだけで、記事データそのものには一切残さない。理由は単純で、featuredImage が空であること自体が「画像がまだ作られていない」という機械可読な印になるからだ。もしフォールバック画像のパスを frontmatter に書き込んでしまうと、「画像あり」と「画像なし」の区別が消える。そうなると後追いで画像を生成する処理は、どの記事が対象なのか二度と見分けられなくなる。穴は空いたまま永久に残る。
止めることをやめた代わりに、「空である」という状態そのものを壊さないことをルールにした。
やってみてわかったこと
- 公開ゲートは「正しさ」と「詰まりやすさ」がトレードオフになる場面がある。在庫を先行生産する構成では、ゲートを足すほど供給全体が脆くなる
- フォールバックは「見た目を埋める」役に徹させ、「実際に画像を作った」記録とは別の場所に置く。混ぜると自動修復の手がかりが消える
- 事故(画像なし公開)をゼロにする設計より、事故が起きても供給が止まらず自己修復する設計のほうが、無人運用では効いた
更新履歴
- 2026-07-31: 初出。
- 2026-07-31: 公開後レビューの指摘を受け、対象が版元の別媒体(TB)であることを明示(#969)。