自分の作っているプラグインで、画像が古いまま返る不具合を見つけました。
原因は単純で、修正も1行でした。
ただ、見つかるまでの筋道と、なぜ気づきにくいのかのほうが書く価値があると思ったので、そちらを中心に残します。
症状
メディアライブラリで画像を完全に削除し、同じ名前で別の画像をアップロードする。
すると、古いほうの画像が返り続けます。
ブラウザキャッシュを消しても直りません。
そして、404 になりません。 200 で、古い画像が正常に返ります。
切り分け
まず、返っているものを測る
画面で確認しても分かりません。配送経路に何段も挟まっているので。
実際に返っているものを見ます。
| 1 2 3 | curl -sI -H "Accept: image/avif,image/webp,*/*" \ "https://example.com/wp-content/uploads/2026/09/fig-a-1024x536.png" \ | grep -i -E "^HTTP|content-type|content-length" |
| 1 2 3 | HTTP/2 200 content-type: image/avif content-length: 17746 |
PNG を要求して、AVIF が返っています。
そのバイト数を、ファイルと突き合わせる
| 1 | find wp-content/uploads -name "fig-a*" -ls |
| 1 2 | 17746 9月 15 18:12 fig-a-1024x536.png.avif |
一致しました。 そして、fig-a-1024x536.png は一覧に存在しません。
寸法で、別画像だと確定する
サイズ違いのファイル名には寸法が入るので、縦横比で世代を見分けられます。
| 1 2 | 18:12 768x402 / 1024x536 / 1536x804 / 300x157 比 1.91 18:13 768x432 / 1024x576 / 1536x864 / 300x169 比 1.78 |
18:12 のサイズ違いは、すべて .avif だけが残っていました。
PNG は消えている。AVIF は消えていない。
原因
.htaccess に書いていた、振り替えのルールです。
| 1 2 3 4 | RewriteCond %{HTTP_ACCEPT} image/avif RewriteCond %{REQUEST_URI} \.(jpe?g|png)$ [NC] RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.avif -f RewriteRule ^(.+)\.(jpe?g|png)$ $1.$2.avif [T=image/avif,E=REQUEST_image,L] |
3行目が、変換後のファイルの存在しか見ていません。
元のリソースがまだあるかどうかは、条件に入っていませんでした。

なぜ孤立するのか
WordPress の削除処理が消すのは、添付ファイルのメタ情報に載っているものだけです。
元のファイルと、sizes に登録されたサイズ違い。それ以外は知りません。
変換後のファイルは、後から別の工程で作られたものです。
メタ情報に載っていないので、削除の対象になりません。
| 1 2 3 4 | fig-a.png 消える メタ情報にある fig-a-1024x536.png 消える sizes にある fig-a.png.avif 残る メタ情報に無い fig-a-1024x536.png.avif 残る メタ情報に無い |
手元の1枚で数えたら、24個のファイルができていました。 そのうち半分が変換後のものです。
修正

条件を1つ足しました。
| 1 2 3 4 5 | RewriteCond %{HTTP_ACCEPT} image/avif RewriteCond %{REQUEST_URI} \.(jpe?g|png)$ [NC] RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.avif -f RewriteRule ^(.+)\.(jpe?g|png)$ $1.$2.avif [T=image/avif,E=REQUEST_image,L] |
元のリソースがあるときだけ、振り替える。
無ければ振り替えず、そのまま 404 になります。
WebP 側にも同じ構造があったので、両方直しています。
削除時に、変換後も消す
ルールを直しただけでは、ファイルは残り続けます。
delete_attachment で、変換後のファイルも対象にしました。
元ファイルと、すべてのサイズ違いに対してです。
命名は拡張子の置換ではなく、後ろに付ける形です。
| 1 2 | fig-a.png → fig-a.png.avif / fig-a.png.webp fig-a-150x150.png → fig-a-150x150.png.avif など |
既存の孤立ファイル
すでに残っているものは、修正では消えません。探して消す必要があります。
| 1 2 3 4 | find wp-content/uploads -name "*.avif" | while read f; do orig="${f%.avif}" [ -f "$orig" ] || echo "$f" done |
いきなり削除する処理にはしていません。 件数を出して、確認してから消す形にしました。
気づきにくい理由
ここが、いちばん書きたかったところです。
1. 404 にならない
ファイルが無ければ 404 になる、という前提で調べます。
200 で返ってくると、配送の問題だと思いません。
2. 保存期間が長い
| 1 | ExpiresByType image/avif A31536000 |
365日です。
原因ではありませんが、症状を長引かせます。
サーバー側を直しても、手元に残っていれば古いまま見えます。
「キャッシュを消しても直らない」と「キャッシュのせいで直ったように見えない」が混ざる。
切り分けが、そこで難しくなります。
3. キャッシュで説明がついてしまう
この症状は、2022年から報告されています。
調べていて、「ブラウザキャッシュだと思います」と回答されて解決済みとして閉じられているものを見かけました。
質問者はリロードもログインし直しも試していました。
それらしい説明が先にあると、そこで止まります。
同じ形の見落とし
PDF のサムネイルが真っ白になる件と、同じ構造でした。
| 1 2 | 白紙の件 受け取った画像が白紙かどうかを、確認していなかった この件 元のリソースがまだあるかどうかを、確認していなかった |
どちらも「確認していない」ことによります。
そして、どちらも普段は問題になりません。
Ghostscript が新しければ白紙にならない。元のファイルが消えなければ孤立しない。
普段は成立する前提を、条件に書いていなかった。 それだけです。
他でも起こりうる構造です
この振り替えの書き方は、変換系のプラグインで広く使われています。
手元で確認した範囲では、複数の実装が同じ条件の書き方をしていました。
ただ、同じ手順で再現を取ったわけではないので、そこは断定しません。
これは欠陥というより、欠落だと思っています。
元ファイルが消えている状況を想定していなかった、というだけです。
学んだこと
存在確認は、片側だけでは足りない
「代わりのものがあるか」だけを見ると、こうなります。
置き換えるなら、置き換えられる側がまだ有効かどうかも見る。
管理外のファイルは、自分で面倒を見る
WordPress のメタ情報に載せずにファイルを作ると、ライフサイクルから外れます。
作った側が、削除まで責任を持つ。
フックを1つ足すだけの話でした。
200 は、正しさの証明ではない
これがいちばんこたえた部分です。
応答コードは、何かが返ったことしか示しません。
それが正しいものかどうかは、別の話でした。
確認した環境
| 1 2 3 | サーバー Xserver 対象 Prime Cache Pro の AVIF / WebP 配信 確認日 2026年9月 |
この件は、修正版で解消しています。
症状と直し方だけ知りたい方向けの記事は、別に書きました。


コメント