Cloudflare R2 Image URL Display Issue: Detailed Explanation and Fix

i have managed to fix this with an updated theme component that has settings for the domains. thus anyone can use it without having to hard code their own component. this works for any s3 compatible storage:

if you do patch it yourself, the initializer should look like this
import { apiInitializer } from "discourse/lib/api";

export default apiInitializer("1.8.0", (api) => {

  // ⚠️ Ensure these are your exact domains!
  // look at the image paths for post thumbnail & chat thumbnail and compare!

  const badDomain = "yoursite-uploads.xxx.r2.cloudflarestorage.com";
  const goodDomain = "uploads.yoursite.com";

  function fixImage(img) {
    if (img.src && img.src.includes(badDomain)) {
      img.src = img.src.replace(badDomain, goodDomain);
    }
    if (img.dataset?.src && img.dataset.src.includes(badDomain)) {
      img.dataset.src = img.dataset.src.replace(badDomain, goodDomain);
    }
  }

  // 1. Safely fix images rendered in standard posts/HTML
  api.decorateCooked($elem => {
    const container = $elem[0];
    if (!container) return;
    
    container.querySelectorAll(`img[src*="${badDomain}"]`).forEach(fixImage);
  }, { id: 'fix-r2-chat-bug' });

  // 2. High-performance observer strictly for dynamically injected Chat elements
  const observer = new MutationObserver(mutations => {
    for (const mutation of mutations) {
      
      // If a new chat message or element is injected into the DOM
      if (mutation.type === 'childList') {
        for (const node of mutation.addedNodes) {
          if (node.nodeType === 1) { // ELEMENT_NODE
            if (node.tagName === 'IMG') fixImage(node);
            
            // Only search INSIDE the new node, not the whole document
            node.querySelectorAll(`img[src*="${badDomain}"]`).forEach(fixImage);
          }
        }
      } 
      // If an existing image's src attribute changes (lazy loading)
      else if (mutation.type === 'attributes') {
        if (mutation.target.tagName === 'IMG') {
          fixImage(mutation.target);
        }
      }
    }
  });

  // Start the observer immediately
  observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeFilter: ['src', 'data-src']
  });
});
1 Like