Makuhari Development Corporation
5 min read, 943 words, last updated: 2025/11/18
TwitterLinkedInFacebookEmail

Solving Safari Responsive Image Display Issues from Google Search Results

The Problem

A developer encountered a peculiar issue with responsive images on their Single Page Application (SPA): images using media queries for responsive design would fail to display only under very specific conditions:

  • Browser: iOS Safari only
  • Entry point: Google search results
  • Occurrence: Second visit and beyond (first visit works fine)
  • Other scenarios: All other access methods work normally

The application architecture was straightforward: pure SPA with no Server-Side Rendering (SSR) or Client-Side Rendering (CSR) - essentially static HTML with responsive images.

This type of issue is particularly frustrating because it's intermittent and platform-specific, making it difficult to reproduce consistently during development.

Investigation

Analyzing the Symptoms

The specificity of the issue provided crucial clues:

  1. Platform-specific: Only iOS Safari affected
  2. Entry-point dependent: Only from Google search results
  3. Visit-sequence dependent: First visit works, subsequent visits fail
  4. Feature-specific: Only responsive images affected

Testing the bfcache Theory

The most likely culprit was Safari's back-forward cache (bfcache) behavior combined with Google's page prerendering. Let's verify this:

// Debug script to detect bfcache restoration
window.addEventListener("pageshow", (event) => {
  if (event.persisted) {
    console.log("Page restored from bfcache");
    console.log("Images may need refresh");
  } else {
    console.log("Fresh page load");
  }
});

When testing this script, the pattern becomes clear:

  • First visit: "Fresh page load" - images display correctly
  • Second visit: "Page restored from bfcache" - images disappear

Understanding the Technical Context

iOS Safari implements aggressive caching when users navigate from Google search results. Google often prerenders pages or Safari uses bfcache to restore pages instantly. However, during bfcache restoration:

  1. JavaScript doesn't re-execute
  2. Media queries aren't re-evaluated
  3. srcset attributes aren't reassessed
  4. Responsive image logic gets skipped

Root Cause

The root cause is a Safari-specific bug in how bfcache restoration handles responsive images:

<!-- This structure fails on bfcache restore -->
<picture>
  <source media="(max-width: 600px)" srcset="mobile.jpg">
  <source media="(min-width: 601px)" srcset="desktop.jpg">
  <img src="fallback.jpg" alt="Responsive image">
</picture>

What happens during bfcache restore:

  1. Safari restores the page DOM instantly
  2. The <source> elements' media attributes aren't re-evaluated
  3. No matching source is found for current viewport
  4. The fallback <img> may not load properly
  5. Result: blank image space

This bug is exclusive to Safari and primarily triggered by:

  • Google search result navigation
  • bfcache restoration
  • Pages using <picture> elements or srcset with media queries

Solution

The most elegant solution forces Safari to re-evaluate responsive images during bfcache restoration:

window.addEventListener("pageshow", (event) => {
  if (event.persisted) {
    // Force re-evaluation of responsive images
    document.querySelectorAll("picture source").forEach(source => {
      const originalSrcset = source.srcset;
      source.srcset = "";
      requestAnimationFrame(() => {
        source.srcset = originalSrcset;
      });
    });
    
    // Also handle img elements with srcset
    document.querySelectorAll("img[srcset]").forEach(img => {
      const originalSrcset = img.srcset;
      img.srcset = "";
      requestAnimationFrame(() => {
        img.srcset = originalSrcset;
      });
    });
  }
});

Method 2: Disable bfcache (Nuclear Option)

If the above doesn't work, you can prevent bfcache entirely:

<meta http-equiv="Cache-Control" content="no-store">

Or via HTTP headers:

Cache-Control: no-store

Trade-off: This disables the performance benefits of bfcache, making back/forward navigation slower.

Method 3: Enhanced Detection and Recovery

For production environments, implement comprehensive detection:

class SafariImageFix {
  constructor() {
    this.isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
    this.isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
    
    if (this.isIOS && this.isSafari) {
      this.init();
    }
  }
  
  init() {
    window.addEventListener("pageshow", this.handlePageShow.bind(this));
  }
  
  handlePageShow(event) {
    if (event.persisted) {
      this.refreshResponsiveImages();
    }
  }
  
  refreshResponsiveImages() {
    // Refresh picture elements
    document.querySelectorAll("picture").forEach(picture => {
      const sources = picture.querySelectorAll("source");
      sources.forEach(source => {
        const srcset = source.srcset;
        source.srcset = "";
        source.srcset = srcset;
      });
    });
    
    // Refresh img elements with srcset
    document.querySelectorAll("img[srcset]").forEach(img => {
      const srcset = img.srcset;
      img.srcset = "";
      img.srcset = srcset;
    });
    
    // Trigger a reflow to ensure re-evaluation
    document.body.offsetHeight;
  }
}
 
// Initialize the fix
new SafariImageFix();

Method 4: CSS-Only Workaround (Limited)

For simple cases, you might avoid the issue by using CSS media queries instead of HTML media attributes:

.responsive-image {
  display: none;
}
 
@media (max-width: 600px) {
  .responsive-image.mobile {
    display: block;
  }
}
 
@media (min-width: 601px) {
  .responsive-image.desktop {
    display: block;
  }
}
<img src="mobile.jpg" class="responsive-image mobile" alt="Mobile version">
<img src="desktop.jpg" class="responsive-image desktop" alt="Desktop version">

Lessons Learned

Prevention Tips

  1. Always test on iOS Safari: This browser has unique behaviors that don't appear in other WebKit browsers
  2. Test bfcache scenarios: Use browser dev tools to simulate bfcache restoration
  3. Monitor entry points: Different traffic sources can trigger different browser behaviors
  4. Implement comprehensive logging: Track when images fail to load and under what conditions

Best Practices

  1. Graceful degradation: Always provide fallback mechanisms for responsive images
  2. Performance monitoring: Track image load failures in analytics
  3. User agent detection: Implement Safari-specific fixes when necessary
  4. Regular testing: iOS Safari updates can introduce new quirks

Development Workflow

// Debug helper for development
if (process.env.NODE_ENV === 'development') {
  window.addEventListener("pageshow", (event) => {
    console.log(`Page ${event.persisted ? 'restored from bfcache' : 'loaded fresh'}`);
    
    // Check for missing images
    document.querySelectorAll("img").forEach(img => {
      if (!img.complete || img.naturalWidth === 0) {
        console.warn("Image failed to load:", img.src);
      }
    });
  });
}

The Safari responsive image bfcache bug is a perfect example of how modern web development requires platform-specific solutions. While frustrating, understanding the root cause allows us to implement targeted fixes that maintain performance while ensuring consistent user experience across all browsers and entry points.

By implementing the pageshow event listener solution, you can resolve this issue without sacrificing the performance benefits of bfcache, ensuring your responsive images work reliably regardless of how users navigate to your site.

Makuhari Development Corporation
法人番号: 6040001134259
サイトマップ
ご利用にあたって
個人情報保護方針
個人情報取扱に関する同意事項
お問い合わせ
Copyright© Makuhari Development Corporation. All Rights Reserved.