What is the solution for missing images when serving Webp from server and using Cloudflare?

This issue is well reported on here but I cant seem to find a solution to overcome this issue, so this is not a question on why its happening as the other posts explain it, but rather what is the solution as other posts don’t suggest the best solution.

In summary, if you serve Webp from your own server (e.g. using Wordpress Imagify and .htaccess to serve Webp) AND use Cloudflare, then your images will break in non-Webp supported browsers due to Cloudflare caching the image as the Webp version but serving them to Safari users even though they are not supported.

My current solution is not use Cloudflare, then all works OK. If we want to use Cloudflare we cant have Webp at all, and so lose points in Google, so we decide serving Webp is more important that using Cloudflare.

Ideally we would want to use both, and use the Cloudflare Free plan, because the $20 plan is not a solution for most of our web hosting customers.

So on the Free plan, what is the solution to overcome this? So we can use Cloudflare AND serve Webp from our own website? Is it just not posisble, so we can’t use Cloudflare?

Thanks!

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.

Yes, it is possible to serve WebP images at the origin while on a Cloudflare Free Plan, but it takes some coding at the origin.

You need to have both the original JPG/PNG images and, on separate URLs, the WebP images where WebP is smaller. What you do then is to inline a JavaScript that will test whether of not the browser is compatible with WebP and rewrite the URLs for the image requests. So your original HTML will request only JPG/PNG (compatible with all browsers), but the JavaScript will rewrite the URLs and request WebP after testing for browser compatibility. Since the JavaScript is included in the HTML (important!), it shouldn’t create any delays.

Your problem is to either do the proper coding, or find a plugin solution that will do that for you. I’ve tested one WordPress plugin that has an adequate solution, though it may charge you for image optimization depending on whether you need it for optimization or only for WebP conversion.

1 Like

Ok thanks. So only a JS solution available? With JS wont the original image load first, and then the webp if supported? So in fact two images loaded which defeats the object of using the webp in the first place (reduce file size/bandwidth). Or actually you mean dont even load any image as default and load with JS?

We use .htaccess to serve the images. The images are the same URLs as the PNGs/JPGs but with just .webp on the end so its easy to do a rewrite in htaccess. This is the htaccess code. I guess this code is not even used as Cloudflare just serves the .webp so not even hitting our server, whereas JS being in the HTML is checking the user browser.

# BEGIN Imagify: rewrite rules for webp
<IfModule mod_setenvif.c>
	# Vary: Accept for all the requests to jpeg, png, and gif.
	SetEnvIf Request_URI "\.(jpg|jpeg|jpe|png|gif)$" REQUEST_image
</IfModule>
<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteBase /
	# Check if browser supports WebP images.
	RewriteCond %{HTTP_ACCEPT} image/webp
	# Check if WebP replacement image exists.
	RewriteCond %{REQUEST_FILENAME}.webp -f
	# Serve WebP image instead.
	RewriteRule (.+)\.(jpg|jpeg|jpe|png|gif)$ $1.$2.webp [T=image/webp,NC]
</IfModule>
<IfModule mod_headers.c>
	Header append Vary Accept env=REQUEST_image
</IfModule>
# END Imagify: rewrite rules for webp

The creators of WebP offer several methods, but the only one I know that works with a Cloudflare proxy is the JS method. In a Cloudflare setup, you’d need to inline the JS code into the HTML and have images with separate URLs at the origin (e.g., image.jpg for the original, image.jpg.wepb for the WebP-converted image). Then the JS will request the WebP for most browsers, but the original format for non-compatible browsers.

No, and that’s why the JS needs to be inserted into the HTML, and not requested as a separate file. Browser reads the HTML, processes the JS code for WebP compatibility, then requests the images accordingly.

In a way it does affect the bandwidth, by increasing the size of HTML page, as the JS goes there. But that should more than make up for the saved bandwidth with WebP in most cases. You can always test your setup before fully implementing he solution.

Cloudflare will fetch whatever is requested by the HTML. So the issue here is not so much the WebP images in cache, but the HTML in cache. Your .htaccess seems to count on images being always requested by their original format, then processing the replacement at the origin. Now, that would defeat the whole purpose of using WebP in the first place, as it would imply images should not be cached by Cloudflare.

Another solution that I can think of is to run Cloudflare custom worker that will rewrite image links based on UserAgent (assuming you serve .webp by default and alternative images with .jpg exists):

addEventListener("fetch", event => {
  event.passThroughOnException();
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(req) {
  let res = await fetch(req);

  return transform(req, res);
}

function transform(req, res) {
  const userAgent = req.headers.get("user-agent") || "";

  if (!isHTMLResponse(res) || !isSafari(userAgent)) {
    return res;
  }

  const rewriter = new HTMLRewriter();

  rewriter.on("img", {
    element(e) {
      const src = e.getAttribute("src");

      // rewrite images for Safari
      if (src && src.endsWith(".webp")) {
        e.setAttribute("src", src.replace(/\.webp$/, ".jpg"));
      }
    }
  });

  return rewriter.transform(res);
}

function isSafari(userAgent) {
  // Detect Safari and Webview next
  const webkitRegex = /\s+AppleWebKit\/(\d+)/gim;
  return webkitRegex.exec(userAgent);
}

function isHTMLResponse(res) {
  const type = res.headers.get("content-type");
  if (type === null) {
    return false;
  }

  return type.startsWith("text/html");
}

I haven’t tested the code, for srcset you need additional logic to handle it properly.

2 Likes