CMSに上げた画像は自動で軽くならない——ヘッドレスCMSのメディア配信で見落とす4点
ヘッドレス構成でも画像は勝手に最適化されない。変換を通る画像と通らない画像の違い、変換が静かに止まる壊れ方、1年immutableだから「上げ直し」になる理由、消す前の確認まで。自分のブログを実際に直し、一覧12枚を578KBから78KBにした記録つき。

自分のブログの一覧ページを開いて、画像だけ数えたことがある。記事サムネが12枚、全部PNGの原寸で、合計592,209バイト。テンプレートを直して変換に通したら、同じ12枚が79,656バイトになった。7分の1以下。CMSに入れた画像でも、軽くする処理が勝手に走るわけではない。
ヘッドレス構成に移すとき「画像はCMS側でよしなに最適化される」と思われがちで、ここがずれると納品後にじわじわ効いてくる。先に決めておきたい4点をまとめる。
変換を通る画像と、通らない画像がある
同じサイトの中でも、画像の出方は2種類に分かれる。
- 本文(リッチテキスト)の画像 —
/_image?href=...&w=640&f=webpのような変換URLで、srcset付きで出る - テンプレートに自分で書いた
<img src={...}>— CMSのファイルURLを直接指すだけ。原寸のまま届く
自分のサイトが後者だった。記事のヒーロー画像は <img src="/_emdash/api/media/file/....png"> の一行で、srcsetもwidth/heightも無い。48,605バイトのPNGが、ページで一番大きい画像としてそのまま配られていた。実績カードだけは変換を通してあった。ブログ側が取り残されていた形。
直し方は emdash/ui の Image コンポーネントに置き換えるだけ。ただし置き換えただけでは足りない。sizes を書かないと表示実寸より大きい変種が選ばれるので、レイアウトの実寸を数えて渡す。うちは一覧カードが最大552px、記事本文が最大672pxだった。
<Image
image={post.data.featured_image}
sizes="(min-width: 1200px) 552px, (min-width: 769px) 46vw, 92vw"
/>冒頭の79,656バイトはこの状態で測ったもの。記事のヒーローも、48,605バイトのPNGが表示実寸に合う750px幅のWebPで10,008バイトになった。ここは記事のLCPになるので、priority(eager と fetchpriority=high)も付けた。
見るべきは「CMSに入れたかどうか」ではなく、テンプレートのどの <img> が変換を通っているか。まずこの一覧を作る。表示が遅いときの切り分け手順はLCPが遅い原因は「内訳」でわかるに書いた。

変換が止まっても、画面はふつうに出る
Cloudflare Workers に載せる構成では、リサイズと再エンコードはWorkerの中で IMAGES バインディング経由で走る。このバインディングは自分で宣言しなくても @astrojs/cloudflare がビルド時に足してくれる。条件はひとつ、ランタイムの画像サービスが cloudflare-binding(未設定を含む)のときだけ。passthrough や compile などを指定すると付かない。
怖いのは、無くなったときの壊れ方が静かなこと。
- 内部の
/_emdash/api/media/file/…経由 — 変換されない原寸がそのまま返る。ページは普通に表示され、ログにも何も出ない - R2のバケットURL経由 — アダプタ側の変換エンドポイントが500を返す
前者は誰も気づけない。見た目が出ている、は最適化されている証拠にならない。デプロイに実際に入る設定は、.wrangler/deploy/config.json が指す生成済みの wrangler.json に images の項目があるかで確認できる。
課金の形も知っておきたい。Cloudflareは「ソース画像とパラメータの組み合わせ」単位で数え、同じ組み合わせは月に1回だけ課金する。Images Free は月5,000ユニークまで。超えるとキャッシュ済みの変換は配信され続けるが、新しい変換は 9422 エラーになる。記事が増えるほど組み合わせも増える。srcsetの刻みを無闇に増やさない設計が効く。

画像は「差し替え」ではなく「上げ直し」
配信ヘッダーを1回見ておくと、運用ルールが決まる。自サイトのメディアはこう返ってくる。
Cache-Control: public, max-age=31536000, immutable1年、しかも immutable。同じURLの中身を後から替えても、一度見た人のブラウザは取りにこない。だから運用は「上書き」ではなく「新しく上げて、記事の参照を貼り替える」になる。
ファイルが無尽蔵に溜まる心配は少ない。EmDashはアップロードを内容のハッシュで重複排除するので、まったく同じバイト列を上げ直すと既存のアイテムが返る。
クライアントに渡すときは、この一言を添えておくと問い合わせが減る。「同じファイル名で上げ直しても直りません。新しい画像として上げて、差し替えてください」。

消す前と、渡す前に決めておく
メディアの削除がいちばん事故りやすい。ファイルを消しても本文側の参照は消えないので、記事の画像が黙って壊れる。
EmDashにはメディアの「Used in(どこで使われているか)」があり、管理者が設定から使用状況トラッキングを有効にすると使える。注意が2つ。一度オンにすると戻せないこと。そして、このリストが見ているのはEmDashのコンテンツ内の参照だけで、テーマのコードや外部サイトからの参照までは追えないこと。空欄イコール消して安全、とは読まないでほしい。
渡す前に握っておくと揉めない項目。
- 受け付ける形式と上限。既定は1ファイル50MBで、画像・PDF・動画・音声の許可リスト。SVGは画像/ファイルフィールドで許可すれば通せる
- altを誰が書くか。CMS側で入力必須にするのか、実装側で埋めるのか
- 一覧に出る画像の縦横比と、書き出しの推奨サイズ。ここは書き出した画像がぼやける・ガタつくをそのまま渡すのが早い
- 画像を本文リッチテキストに埋めさせるか、専用フィールドに分けるか(ヘッドレスCMSのフィールド設計)
画像1種類の扱いで、ページの重さは数百KB変わる。「画像が重い」「差し替えたのに反映されない」が起きているなら、配信の経路を1本ずつ辿れば原因は絞り込める。実装や改修でお困りでしたら、お問い合わせからご相談ください。
本記事は2026年9月時点の情報です。