Cloudflares Mirage 2.0 broke my images on all mobile devices

I'm writing this (hopefully short) blog post to warn others about the situation I found myself in the other day. As I've mentioned in my previous posts, I recently migrated everything from Jekyll and GitHub Pages to 11ty and Cloudflare Pages. This was quite a significant migration that took a while to complete! When I started the migration, I was on Cloudflare's Free plan. As I have been for several years. This is important, and I will revisit this point later!

Once the initial migration was completed, I tested the new code, design, and functionality on every device I could get my hands on, just to make sure there weren't any obvious bugs I'd missed. In doing so, I actually found a cross-browser layout bug where the latest Firefox and latest Chrome differed. I was quite surprised by this, but it shows you should always test on different browsers! Especially ones with differing rendering engines! Anyway, I'm rambling off-topic again!

By this point, I thought everything was looking pretty stable. I had the basics in place, the site was performing well, and accessible (according to Lighthouse anyway!), I'd locked it down in terms of security (another important point!).

New year, New Cloudflare Plan

Unknown to me, I'd set up an unfortunate chain of decisions that would lead to my images being broken on all mobile devices for exactly 3 weeks without me knowing (I know, a bit dramatic, and a very 1st world problem!).

In December, I'd decided to pay annually for the Pro Cloudflare plan for 2025. Unfortunately for me, what they don't mention when this happens is that certain "Pro-only" functionality is enabled with this upgrade process. One of those features happens to be a performance optimisation called Mirage 2.0. I do love the image that they have used in that blog post, and its clever play on words.

Mirage 2.0

Mirage 2.0 is Cloudflare's latest version of the image optimisation library. Mirage 1.0 was released in June 2012.

Cloudflare's Mirage 2.0 is a web performance optimisation tool focused on enhancing image delivery for mobile users by reducing page load times and bandwidth usage. It uses techniques like lazy loading and adaptive image resizing to serve images tailored to users' devices and network conditions, ensuring faster load times even on slow connections. Mirage dynamically prioritises image loading, starting with low-resolution placeholders and upgrading to higher-quality versions as needed, optimising user experience while conserving data. Integrated into Cloudflare's CDN, Mirage 2.0 provides seamless optimisation without extra effort from website operators.

The emphasis in the above paragraph is mine. Just to be clear, I had no idea Mirage 2.0 even existed until the day all my images were randomly broken on mobile devices!

Event timeline

So what exactly happened to get to this point? Well, I:

  1. Migrated from Jekyll and GitHub Pages to 11ty and Cloudflare Pages.
  2. Completed the site migration, optimised web performance, accessibility, and security.
  3. Tested my shiny new site in numerous browsers (including mobile browsers).
  4. Upgraded to the Cloudflare Pro Plan.
  5. Mistakenly (maybe?), didn't retest the site across browsers after the upgrade (since I assumed nothing would change regarding the platform and environment!).
  6. Made sure site still rendered perfectly on Desktop (yay!), and discovered the Pro plan even allowed me to enable Brotli compression for all assets! Winning!
  7. Was then informed by Matteo Contrini that my images were broken on mobile off the back of announcing my new blog post on Bluesky.
  8. Panicked, confusion… argh!

It's point 8 where the self-doubt creeps in:

  • "Have the images ever worked on the site??"
  • "I'm 100% sure I tested on mobile!"
  • "Surely someone would have mentioned this to me if they'd never worked??"
  • "Why does it work on desktop but not on mobile??"
  • "Why do the images work when I request the desktop site on mobile!!"
  • "Is it the new eleventyImageTransformPlugin? If so, surely, it would have been spotted and fixed already!"
  • "How on earth am I going to debug and resolve this issue?"

That list of quotes above was basically my thought process for a couple of hours after the image problem was reported!

Web Inspector for iOS

Thankfully, Matteo jumped in and helped with the final quote on the list above when he pointed me in the direction of the fantastic Web Inspector App for iOS. Once installed and enabled as a Safari extension, it allows you to easily debug web pages on iOS Safari. It comes with tabs for the:

  • DOM
  • Elements
  • Console
  • Network
  • Resources

Screenshot from my mobile showing the really handy Web Inspector App on show.

In fact, if you look at the code seen in the inspector in the image, you will see how I finally worked out what was going on!

Script Injection

On upgrading to Cloudflare Pro, unbeknown to me, the Mirage functionality is automatically enabled within the Cloudflare dashboard! So every image on every page has the mirage 2.0 script injected into the DOM (but only on mobile! Remember my added emphasis in the Mirage 2.0 section earlier). Also remember how I'd locked down my blog's security using HTTP response headers. Which just so happened to include a Content Security Policy (CSP)!

Script Injection + CSP equals pain

I think most readers may realise where I'm going with this now! On the one hand, it's fantastic to know that the CSP did its job and blocked the 3rd party script from ajax.cloudflare.com! I mean, that's what it's there for, after all! But what's not so great is the fact that this injection only happens on a mobile device where there are no console errors to be seen (by default anyway). It's also worth mentioning that at this point it was still a complete guess that this was the issue, as even with the Web Inspector Extension installed. There was no usual CSP error (Content Security Policy: The page's settings blocked the loading of a resource at...) in the console, like you get in a desktop browser! So I was just hoping that this was the issue!

Disabling Mirage

So, the easiest way to resolve the issue is to disable Mirage 2.0 from the Cloudflare Dashboard, easier said than done at times! Cloudflare I adore your platform and the features you offer, but your dashboard navigation is just nuts! Please consider a major overhaul of the whole UI! Furthermore, don't hesitate to put me down as a beta tester as well!

So to disable Mirage, first select the domain you want to change, then look for the "Speed" dropdown on the left, then click the "Optimization" link:

The left navigation in the Cloudflare dashboard with the Optimization link highlighted.

Next, look for the "Image Optimization" tab on the page:

The "Image Optimization" tab highlighted within the Speed / Optimization menu.

Lastly, scroll to the bottom of this tab, and you will find the Mirage setting you can toggle on and off:

The Mirage setting you can toggle on and off.

Unsurprisingly, the setting is now turned off on my dashboard! Also note the "Beta" tag in orange next to the title…

What have we learned?

So, what have we learned from my stressful little adventure?

  1. CSP's actually work really well for blocking third-party JavaScript code injection!
  2. Be sure to check your website often, especially if you happen to change your Cloudflare subscription plan.
  3. Never fully trust anyone, not even your CDN or hosting provider! Even they may choose to meddle with your site's code!
  4. The Web Inspector App for iOS is really useful for debugging mobile Safari issues.

Steps Cloudflare can take to prevent this

So, I'm pretty sure this isn't my fault, right?? I'd like to think I've just been unlucky with having a particular setup and a unique set of circumstances. Saying that, I'm certain that Cloudflare can introduce some changes to stop this from happening to others! My recommendations would be:

  1. Ensure that Mirage 2.0 is Opt-in only and not Opt-out. Especially for a feature that is still in Beta!
  2. When a user upgrades their plan, consider emailing them, or pointing out the new features that are available to them with the new plan (maybe within their dashboard?)
  3. Consider some sort of automation or logging to check to see if Mirage (or other functionality) is causing, or could cause issues if the site has a CSP in place.
  4. Don't assume that your paying customers know everything about every product on your platform.
  5. Finally, it's also worth mentioning to users that there have been similar image issues caused by Mirage in the past. Documented here and here. Although, admittedly, these are related to Vercel and Next.js, so a fairly different setup!

I hope that this blog post isn't coming across too harshly. As I've said before, I really love Cloudflare as a company, and the services they provide. It's just when they overstep the mark and make assumptions about the websites they are hosting!

One final point, I briefly chatting to Andy Davies from Speedcurve on Bluesky. He mentioned they (Speedcurve) recommend all clients that use Cloudflare disable both Mirage AND Rocket LoaderSource

Summary

If you made it to the end of this post, congratulations—you deserve a medal! Meanwhile, I've learned I couldn't write a short blog post if my life depended on it!

Nevertheless, I hope you found it a useful read, and it helps someone who stumbles across this completely unrelated set of circumstances! As always, thanks for reading, and if you have any feedback or comments, please do let me know!


Post changelog:

  • 07/01/25: Initial post published.

Webmentions

No mentions yet.

A webmention is a way to notify this site when you've linked to it from your own site.