.htaccesspngjpegwebpfallback

htaccess: deliver jpg or png if browser does not accept webP


A WordPress website delivers webp. As a fallback for old browsers, jpg or png should be delivered if webp is not accepted.

Each image file is available as webp and jpg or png. With same name but different filetype.

So: image.webp plus image.jpg or image.png

With the following code I would at least like to have JPG delivered if webp is not running. But there is a problem somewhere:

<ifModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_ACCEPT} !image/webp
RewriteCond %{REQUEST_URI} (?i)(.*)(\.webp)$
RewriteCond %{DOCUMENT_ROOT}%1.jpg -f
RewriteRule (?i)(.*)(\.webp)$ %1\.jpg [L,T=image/jpeg]
</IfModule>

Expected the code above to work. But it doesn't and I'm stuck now.

Can anyone help? Where is my mistake?

Edit: the code is correct now and works


Solution

  • From @pixx's answer...

    <ifModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_ACCEPT} !image/webp
    RewriteCond %{REQUEST_URI} (?i)(.*)(\.webp)$
    RewriteCond %{DOCUMENT_ROOT}%1.jpg -f
    RewriteRule (?i)(.*)(\.webp)$ %1\.jpg [L,T=image/jpeg]
    RewriteCond %{REQUEST_URI} (?i)(.*)(\.webp)$
    RewriteCond %{DOCUMENT_ROOT}%1.png -f
    RewriteRule (?i)(.*)(\.webp)$ %1\.png [L,T=image/png]
    </IfModule>
    

    You are missing the HTTP_ACCEPT check on the second rule. So this would result in any request for a .webp file, where the .png file also exists, serving the .png file instead.

    However, these rules can also be simplified/optimised...

    As for where to put these rules (before or after the WordPress code block), it doesn't really matter. By placing these rules after the WP code block they won't be unnecessarily processed for the page request itself, which is a good thing, but it's very minor.

    Bringing the above points together, the rules can be rewritten as follows...

    Note that I've created an additional 1st rule that simply skips the next two rules (that perform the actual file checking/rewriting) if its not a .webp request OR it is a .webp request and image/webp is supported. This is an optimisation, since it will catch most requests and avoid unnecessarily processing the following two rules (that contain the "expensive" filesystem checks). So by the time we get to the second rule we already know that it must be a .webp request and image/webp is not supported.

    RewriteEngine On
    
    # Skip the next 2 rules if not a ".webp" request
    # OR it is a ".webp" request and the client supports "image/webp"
    RewriteCond %{REQUEST_URI} !\.webp$ [NC,OR]
    RewriteCond %{REQUEST_URI}@%{HTTP_ACCEPT} \.webp@.*image/webp
    RewriteRule ^ - [S=2]
    
    # Serve ".jpg" instead...
    RewriteCond %{DOCUMENT_ROOT}/$1.jpg -f
    RewriteRule (.+)\.webp$ $1.jpg [NC,T=image/jpeg,L]
    
    # Serve ".png" instead...
    RewriteCond %{DOCUMENT_ROOT}/$1.png -f
    RewriteRule (.+)\.webp$ $1.png [NC,T=image/png,L]
    

    You could combine this into 1 rule, but it does not make it more efficient (in fact, probably less so). And it's arguably more complex and harder to read. For example, you could do something like this instead:

    # If ".webp" not supported then serve ".jpg" or ".png" instead...
    RewriteCond %{HTTP_ACCEPT} !image/webp
    RewriteCond jpg@jpeg ([^@]+)@(.+)
    RewriteCond %{DOCUMENT_ROOT}/$1.%1 -f [OR]
    RewriteCond png@png ([^@]+)@(.+)
    RewriteCond %{DOCUMENT_ROOT}/$1.%1 -f
    RewriteRule (.+)\.webp$ $1.%1 [NC,T=image/%2,L]
    

    A (minor) downside with this rule is that there are always 2 filesystem checks if image/webp is not supported. If the corresponding ".jpg" file exists then the same filesystem check (for the ".jpg" file) is performed twice (although you would hope the 2nd check is from cache). Whereas with the multi-rule approach (shown earlier) then there are only 2 filesystem checks if image/webp is not supported and the corresponding ".jpg" file does not exist (in which case it tests for the ".png" file).

    A slight complexity with the single rule comes about because of the difference between the jpg file extension and the corresponding mime-type jpeg. Both these need to be "stored" (the file extension in the %1 backreference and mime-type in %2).

    The processing of the conditions is reliant on the fact that the 2nd OR'd condition is not processed if the 1st OR'd condition is successful (a standard optimisation). So, in the above rule, if the first filesystem check (for a .jpg file) is successful then the following condition that sets the captured backreferences to png is not processed, so %1 and %2 remain as jpg and jpeg respectively, ready to be processed by the next condition and ultimately used in the RewriteRule substitution and flags arguments.