2026年9月4日に、このブログの画像を全部 WebP に入れ替えました。289ファイル・75.69MB が 6.61MB になり、91.3%減です。ただ、そのあと「WebP にしていなかった1枚」が原因で X のリンクカードが壊れる事故を起こしました。
この記事は、一般論としての画像最適化ではなく、自分のサイトで実際にやった移行の記録と、そこで踏んだ落とし穴です。数字はすべて wadokon.com の実データです。
移行前|PNGが289ファイル・75.7MBあった
このブログのアイキャッチは、HTMLを書いてヘッドレスChromeでスクリーンショットを撮る方法で作っています。--screenshot の出力は PNG なので、何も考えないと PNG がそのまま本番に上がります。それを続けた結果が次の状態でした。
- PNG 289ファイル(WordPressが自動生成するサムネイル込み)で 75.69MB
- フルサイズのアイキャッチ1枚が 814〜1,101KB
写真ベースの画像を PNG で持つのは、可逆圧縮に写真を通しているだけなので単純に無駄です。1MB近いアイキャッチが記事一覧に何枚も並ぶ状態で、ここを直さずに他のチューニングをする意味はありませんでした。
形式選び|同じ1枚を4形式で保存して比べた
WebP と AVIF のどちらにするか決めるため、実際に使っているアイキャッチ(1200×675 の写真ベース)を同じ原本から4形式に保存し直しました。

| 形式 | サイズ | 原本比 |
|---|---|---|
| PNG(原本) | 842 KB | 100% |
| JPEG q84 | 103 KB | 12.2% |
| WebP q84 | 60 KB | 7.1% |
| AVIF q65 | 44 KB | 5.2% |
数字だけ見れば AVIF が最小です。それでも WebP で統一しました。理由は、このサイトの画像がほぼ全部 og:image としてSNSのクローラーに読まれるからです。ブラウザの対応状況だけで決められない領域があり、そこで冒険する理由がありませんでした。この判断が正しかったことは、3日後に別の形で証明されます。
変換は Pillow で一括処理しました。写真ベースなら q84 で目視の劣化はほぼ分かりません。
from PIL import Image
Image.open(src).convert('RGB').save(dst, 'WEBP', quality=84, method=6)
method=6 は圧縮にいちばん時間をかける設定です。変換は1回きりで配信は何万回もあるので、ここをケチる理由はありません。
移行結果|75.69MB → 6.61MB(91.3%減)
同名で PNG と WebP が揃っている289ファイルを突き合わせた結果です。
| 対象 | PNG | WebP | 削減 |
|---|---|---|---|
| 全289ファイル(サムネ込み) | 75.69 MB | 6.61 MB | 91.3% |
| フルサイズ60枚のみ | 33.1 MB | 2.5 MB | 92.5% |
ここで1つ、意図的にやっていないことがあります。元のPNGを削除していません。Google画像検索が旧PNGのURLをインデックスしているため、いま消すと画像側が404になるからです。サーバー容量には余裕があるので、Search Console の画像インデックスが WebP に移るのを確認してから、2026年12月以降に消す予定にしています。バックアップも別ディレクトリに退避済みです。
「置き換えたら即消す」は事故のもとです。配信を切り替えるのと、旧ファイルを消すのは、別のタイミングでやるのが安全でした。
3日後の事故|XがJPEGのアイキャッチを採用しなかった
9月6日に記事を1本公開し、X に投稿したところ、リンクカードが画像なしの小さいサマリー表示になりました。
調べた範囲では、こちら側に問題は見つかりませんでした。
- HTML の
og:imageは正しく出力されている - クローラーのUAで取得しても同じ結果
- 画像URL は 200 で返り、サイズも 1200×675 で要件を満たしている
Cloudflare のログを引いて、ようやく状況が見えました。Twitterbot は投稿直後からその画像を約30秒おきに170回以上、毎回200で全バイト取得していました。取得できていないのではなく、取得したうえで採用していなかったわけです。
違いは形式だけでした。その1枚だけが JPEG で、同じサイトの WebP のアイキャッチは1回の取得で正常に表示されていました。ファイル自体はベースライン 4:2:2 の素直な JPEG で、壊れてはいません。
原因は自分の手順書でした。9月4日に全画像を WebP にしたのに、アイキャッチ生成の手順書に「JPEGに変換する」という古い記述が残っていたのです。移行したのはファイルだけで、手順を直していませんでした。
同じURLで再投稿しても直らない
画像を WebP に差し替えて X に投稿し直しましたが、カードは直りませんでした。Cloudflare のログを見ると、X は再投稿時にHTMLは取り直しているのに、新しい og:image を一度も取りに来ていません。URL単位のカードキャッシュを再利用し、旧JPEGを取り続けていました。
対処法は2つだけです。
- クエリを付けて別URLとして投稿する(
https://example.com/slug/?v=2) - X側のキャッシュが切れるのを待つ
つまりog:image の確認は、投稿する前にやらないと取り返しがつきません。公開直後に必ず実行するようにしたのがこれです。
curl -s -A "Twitterbot/1.0" https://example.com/slug/ \
| grep -oE '<meta property="og:image" content="[^"]*"'
# → .webp のURLであることを確認し、続けてそのURLを GET して 200 を確認する

差し替えは必ず新しいファイル名で
同名で上書きすると、CDNのエッジキャッシュ(このサイトは Cloudflare で7日)とSNS側のキャッシュに残った旧ファイルが使われ続けます。差し替えるときは eyecatch-slug-v2.webp のように新しいファイル名で上げるのが結局いちばん速い、というのが今回の結論です。
自己監査したら、まだJPEGが18枚残っていた
この記事を書くにあたって、サイト全体をスキャンし直しました。結果は次のとおりです。
- 記事の本文画像 13枚が JPEG のまま
- 固定ページのスクリーンショット 2枚が JPEG のまま
- アイキャッチ 3枚が JPEG のまま
合計18枚です。全部 WebP にしたつもりでいましたが、実際には全然できていませんでした。
特にまずかったのが3枚目のアイキャッチです。これは公開予約中の記事のもので、予約日時になれば自動的に公開され、そのまま X に流していたはずでした。数日前に起こした事故と、まったく同じ条件が仕込まれていたことになります。原因も同じで、手順書を直す前に作った画像がそのまま残っていました。
実際にページが読み込んでいる画像を大きい順に並べると、状況がはっきりします。
| ファイル | 形式 | 転送量 |
|---|---|---|
| 本文画像(最大のもの) | JPEG | 283 KB |
| 本文画像(2番目) | JPEG | 217 KB |
| アイキャッチ | WebP | 40 KB |
いちばん軽いのが、いちばん目立つアイキャッチという逆転が起きていました。しかも WordPress は元画像と同じ形式でサムネイルを生成するので、JPEGを1枚上げると、そのサイズ違いが3〜4枚まとめてJPEGで増えます。1枚の見落としが数枚分に膨らむ構造です。
教訓としては、移行作業を「フォルダ内のファイルを変換する作業」だと思っていたのが間違いでした。記事の本文に書かれているURLまで含めて1セットです。定期的に本文をスキャンして非WebPを検出する仕組みにしておくべきでした。
テーマがやってくれること・やってくれないこと
ついでに、公開中のページのHTMLを実際に見て img タグの属性を確認しました。このブログは WordPress + SWELL です。
- できていた
全てのimgにwidthとheightが入っている。ブラウザがこの2つからアスペクト比を計算して領域を先に確保するので、画像読み込みによるレイアウトのガタつき(CLS)はこれで防げます - できていた
srcsetとsizesが自動生成され、画面幅に応じたサイズが選ばれる - できていた
decoding="async"が付いている - 入っていなかった
fetchpriority="high"。ファーストビューの画像に付けると読み込み開始が早まります
遅延読み込みは、src に1×1の透明GIFをdata URIで入れておき、あとで本物に差し替える方式でした。この方式は画面外の画像には効きますが、ファーストビューの画像に適用されると読み込みが1テンポ遅れてLCPが悪化します。幸いアイキャッチは対象外になっていましたが、「テンプレートの画像に一括で遅延読み込みを付ける」は事故になりやすいポイントです。
テーマやフレームワークが面倒を見てくれる範囲は思ったより広い一方、形式の選択と、ファーストビューの優先読み込みは自分で決める必要があるという切り分けでした。
まとめ|運用ルールとして残したこと
一度きりの変換作業では終わらないので、次のルールを手順書に書き込みました。
- 上げる画像は全部 WebP
アイキャッチも本文画像も例外なし - 差し替えは必ず新しいファイル名で
同名上書きはCDNとSNSのキャッシュに勝てない - 公開したらSNSに投稿する前に og:image を確認
投稿してからでは同じURLで直せない - 旧ファイルの削除は移行と分ける
検索エンジンのインデックスが移るまで待つ - 手順書もセットで直す
ファイルだけ移行して手順が古いままだと、必ず1枚だけ混ざる
数字としては75.69MBが6.61MBになったのがこの移行の成果ですが、実際にいちばん効いたのは「WebP以外を上げない」というルールを1つ決めたことでした。判断が要らなくなると、見落としが減ります。
見つかった18枚は、すべて新しいファイル名の WebP に差し替えました。結果は次のとおりです。
| 対象 | 変換前 | 変換後 | 削減 |
|---|---|---|---|
| 本文画像 13枚 | 1,070 KB | 517 KB | 51.7% |
| 固定ページ 2枚 | 327 KB | 158 KB | 51.7% |
| アイキャッチ 3枚 | 303 KB | 152 KB | 49.8% |
| 合計 18枚 | 1,700 KB | 827 KB | 51.4% |
ここで注目したいのは、削減率が91%ではなく51%にとどまっていることです。最初の移行はPNGからの変換だったので9割減りましたが、今回はすでに非可逆圧縮されたJPEGからの変換なので、削れる余地はその半分しかありません。
つまり最初からWebPで上げていれば得られたはずの差が、あとから変換しても取り戻せないということです。JPEGを一度経由した画像は、WebPにしても「JPEGの劣化を含んだWebP」にしかなりません。運用ルールを先に決めておく価値はここにあります。
変換後の画質は、before/afterのスクリーンショットのような文字が多い画像も含めて、原寸で見比べて劣化が分からないレベルでした。写真ベースの画像なら q84 で十分です。
最終的にサイトに残っている非WebPの画像は2種類だけになりました。
- サイトアイコン(PNG)
ブラウザやOSが読む用途なので、そのまま残す - プロフィールアイコン(JPEG・24KB)
WebPに変換して測ったら23KBにしかならなかったので、据え置いた
最後の1枚は、ルールとしては変換すべきものです。ただ270×270の小さな画像で、変換しても1KBしか変わりません。効果を測ってから決めるという原則のほうを優先しました。全部そろえること自体が目的になると、意味のない作業が増えます。
なお、ここまでやって画像の転送量を91%削減したあと、実際にLighthouseで計測したらスコアは55点でした。画像はもう犯人ではなく、別のものが足を引っ張っていました。その調査は画像を91%減らしてもLighthouseは55点だったにまとめています。
次に読む記事



