自分のブログの一覧ページを開いて、画像だけ数えたことがある。記事サムネが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/uiImage コンポーネントに置き換えるだけ。ただし置き換えただけでは足りない。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が遅い原因は「内訳」でわかるに書いた。

変換を通る本文の画像と、通らないテンプレートのimgの比較表

変換が止まっても、画面はふつうに出る

Cloudflare Workers に載せる構成では、リサイズと再エンコードはWorkerの中で IMAGES バインディング経由で走る。このバインディングは自分で宣言しなくても @astrojs/cloudflare がビルド時に足してくれる。条件はひとつ、ランタイムの画像サービスが cloudflare-binding(未設定を含む)のときだけ。passthroughcompile などを指定すると付かない。

怖いのは、無くなったときの壊れ方が静かなこと。

  • 内部の /_emdash/api/media/file/… 経由 — 変換されない原寸がそのまま返る。ページは普通に表示され、ログにも何も出ない
  • R2のバケットURL経由 — アダプタ側の変換エンドポイントが500を返す

前者は誰も気づけない。見た目が出ている、は最適化されている証拠にならない。デプロイに実際に入る設定は、.wrangler/deploy/config.json が指す生成済みの wrangler.json に images の項目があるかで確認できる。

課金の形も知っておきたい。Cloudflareは「ソース画像とパラメータの組み合わせ」単位で数え、同じ組み合わせは月に1回だけ課金する。Images Free は月5,000ユニークまで。超えるとキャッシュ済みの変換は配信され続けるが、新しい変換は 9422 エラーになる。記事が増えるほど組み合わせも増える。srcsetの刻みを無闇に増やさない設計が効く。

画像変換が止まったときの壊れ方と確かめ方の4カード

画像は「差し替え」ではなく「上げ直し」

配信ヘッダーを1回見ておくと、運用ルールが決まる。自サイトのメディアはこう返ってくる。

Cache-Control: public, max-age=31536000, immutable

1年、しかも immutable。同じURLの中身を後から替えても、一度見た人のブラウザは取りにこない。だから運用は「上書き」ではなく「新しく上げて、記事の参照を貼り替える」になる。

ファイルが無尽蔵に溜まる心配は少ない。EmDashはアップロードを内容のハッシュで重複排除するので、まったく同じバイト列を上げ直すと既存のアイテムが返る。

クライアントに渡すときは、この一言を添えておくと問い合わせが減る。「同じファイル名で上げ直しても直りません。新しい画像として上げて、差し替えてください」。

画像を差し替えるときの手順4ステップ

消す前と、渡す前に決めておく

メディアの削除がいちばん事故りやすい。ファイルを消しても本文側の参照は消えないので、記事の画像が黙って壊れる。

EmDashにはメディアの「Used in(どこで使われているか)」があり、管理者が設定から使用状況トラッキングを有効にすると使える。注意が2つ。一度オンにすると戻せないこと。そして、このリストが見ているのはEmDashのコンテンツ内の参照だけで、テーマのコードや外部サイトからの参照までは追えないこと。空欄イコール消して安全、とは読まないでほしい。

渡す前に握っておくと揉めない項目。

  • 受け付ける形式と上限。既定は1ファイル50MBで、画像・PDF・動画・音声の許可リスト。SVGは画像/ファイルフィールドで許可すれば通せる
  • altを誰が書くか。CMS側で入力必須にするのか、実装側で埋めるのか
  • 一覧に出る画像の縦横比と、書き出しの推奨サイズ。ここは書き出した画像がぼやける・ガタつくをそのまま渡すのが早い
  • 画像を本文リッチテキストに埋めさせるか、専用フィールドに分けるか(ヘッドレスCMSのフィールド設計

画像1種類の扱いで、ページの重さは数百KB変わる。「画像が重い」「差し替えたのに反映されない」が起きているなら、配信の経路を1本ずつ辿れば原因は絞り込める。実装や改修でお困りでしたら、お問い合わせからご相談ください。

本記事は2026年9月時点の情報です。