.htaccessによるWebPの選択的レスポンスとその問題点と改善案

nginx向けの設定をこちらに書きました。

Webサーバー上のJpegやPNGファイルについて、そのWebP版ファイルがある場合はそちらを返す(もちろんブラウザが対応している場合のみ)、という.htaccessの記述例はすでにいろいろ公開されています。

多少の差違はありますが、たいてい以下のような方法をとっています。

WebP-images-with-htaccess

※ 2018/10/31追記 Varyヘッダの送出条件に関するプルリクがマージされました。混乱するので、以前のコミットにリンク先を変更します。

コメントを日本語に置き換えて説明します。


WebP選択レスポンスでメジャーな.htaccess

<IfModule mod_rewrite.c>

# Rewriteモジュールを有効にする
RewriteEngine On

# ブラウザから送信されるAcceptリクエストヘッダがimage/webpを含む場合のみ
# 後続のRewriteRuleを適用する
RewriteCond %{HTTP_ACCEPT} image/webp

# WebP版のファイルがある場合のみ後続のRewriteRuleを適用する
RewriteCond %{DOCUMENT_ROOT}/$1.webp -f

# *.jpg、*.pngファイルを*.webpファイルに内部的にルーティングする
# Content-Typeはimage/webpにして環境変数acceptを1にする
RewriteRule (.+)\.(jpe?g|png)$ $1.webp [T=image/webp,E=accept:1]
</IfModule>

<IfModule mod_headers.c>
# 環境変数acceptが真の(上記RewriteRuleで1が設定された)場合、
# CDNやキャッシュサーバーがWebPとJpeg/PNGをそれぞれキャッシュできるように
# VaryレスポンスヘッダにAcceptを追加する
Header append Vary Accept env=REDIRECT_accept
</IfModule>

<IfModule mod_mime.c>
# 拡張子.webpファイルはContent-Typeとしてimage/webpを返す
AddType image/webp .webp
</IfModule>


WordPressのEWWW Image Optimizerプラグインが提示する方法なのでメジャー感がありますが、改善案を示してみたいと思います。


改善案について


WebP版ファイルの命名規則の改善提案

個人的には元のファイルの拡張子も含めた上で、.webpを追加する方がよい気がします(例: image.jpg → image.jpg.webp)。


  • 将来WebPを直接出力するケースも増えるのではないか。そのとき、変換によって生成されたファイルなのか(image.jpg.webp)、オリジナルとして出力したWebPなのか(image.webp)区別をつけやすい。

  • ファイル名が重複したとき(logo.pngとlogo.jpgなど)、衝突を避けられる。

無意味な心配や、拡張子が続くのが嫌といった意見もあるかと思います。


.htaccessの配置箇所が固定される

メジャー版の.htaccessファイルは、DocumentRootに設置する前提になっています。例えば/imagesディレクトリに設置すると、1箇所書き換える必要があります。

RewriteCondの工夫でできないかなーと思いましたが、拡張子置き換えの場合は無理そうでした(アイデアがあればコメントでぜひ!)。


メジャーな.htaccessの変更点

  # WebP版のファイルがある場合のみ後続のRewriteRuleを適用する

# サブディレクトリの場合はそのパスが必要
RewriteCond %{DOCUMENT_ROOT}/images/$1.webp -f


WebPに置換したときのみVaryヘッダにAcceptを返している

※ Webサーバから直接画像を配信している場合は影響ありません。

これがちょっとマズくない?と思ったところで、ChromeなどからのアクセスによりRewriteRuleが適用されてWebP版のファイルに置き換えられたときのみ、レスポンスヘッダにVary: Acceptが含まれます。

Firefoxからのアクセス時はVary: Acceptが含まれません。

これって、運悪く最初のリクエストがFirefoxからあってVary: AcceptなしでJpegファイルを返すと、その後ChromeがアクセスしてもキャッシュヒットしちゃってWebPを返さないんじゃない?と思いました。

本来は.jpgや.pngへのアクセス全体にVaryヘッダを返すという実装の方が望ましいのではないでしょうか。そんなことないよ!という場合はご指摘ください。


CloudFrontの場合

試してみましたが、FirefoxにVaryを返さないメジャーな方法でも問題ありませんでした。先にFirefoxでアクセスしてその後ChromeでアクセスしてもJpeg、WebPがそれぞれ返されました。

CloudFrontでは、リクエストヘッダのホワイトリストにAcceptを追加しなければなりませんが(指定しないとそもそもオリジンにリレーされないので)、その作用かもしれません。


以上を踏まえた改善案

メジャー版に対する変更点・改善点は以下の通りです。



  • 変更点 image.jpg→image.webp ではなく image.jpg→image.jpg.webpを前提とします。


    • この変更があるのでEWWW Image Optimizerとはそのまま併用できません。




  • 改善点 DocumentRoot以外の任意のディレクトリに設置しても動作します。


  • 改善点 WebPが返却されなかった場合もVary: Acceptを返却します。


WebPの選択レスポンスのための私的改善版

<IfModule mod_setenvif.c>

# WebP版があるかもしれない画像へのリクエストは全てVaryレスポンスヘッダを返す
SetEnvIf Request_URI "\.(jpe?g|png)$" _image_request
</IfModule>

<IfModule mod_rewrite.c>
# Rewriteモジュールを有効にする
RewriteEngine On

# ブラウザから送信されるAcceptリクエストヘッダがimage/webpを含む場合のみ
# 後続のRewriteRuleを適用する
RewriteCond %{HTTP_ACCEPT} image/webp

# WebP版のファイルがある場合のみ後続のRewriteRuleを適用する
# SCRIPT_FILENAMEを使うことでサブディレクトリの補完が不要に
RewriteCond %{SCRIPT_FILENAME}.webp -f

# *.jpg、*.pngファイルを*.webpファイルに内部的にルーティングする
# Content-Typeはimage/webpにする
# リダイレクト先は $0.webp でも可
RewriteRule .(jpe?g|png)$ %{SCRIPT_FILENAME}.webp [T=image/webp]
</IfModule>

<IfModule mod_headers.c>
# 環境変数_image_requestが真の(上記SetEnvIfが設定された)場合、
# VaryレスポンスヘッダにAcceptを追加する
Header append Vary Accept env=_image_request
</IfModule>

<IfModule mod_mime.c>
# 拡張子.webpファイルはContent-Typeとしてimage/webpを返す
AddType image/webp .webp
</IfModule>