Problem Description
We’ve recently noticed an issue with image display on the forum, specifically:
- Image thumbnails are not displaying correctly.
- Clicking on the images displays the full-size image correctly.
- The browser’s developer tools show incorrect image URLs.
After investigation, the root cause is an incorrect domain name in the image URLs:
- Correct URL:
https://store.starorigin.cc/optimized/1X/[imageID].jpeg
- Incorrect URL:
https://info.7a4081a2d83d3f43fe6b1be1c926fd1c.r2.cloudflarestorage.com/optimized/1X/[imageID].jpeg
The system is using the raw R2 bucket wrong domain instead of our configured CDN domain.
Technical Analysis
This is a known issue with Discourse when handling images stored in Cloudflare R2. In some cases, even when s3_cdn_url
is configured, Discourse might still use the raw storage URL instead of the CDN URL when generating optimized images (like thumbnails).
This could be related to the following factors:
- Discourse version
- The configuration of the S3-compatible storage
- How URLs are stored in the
OptimizedImage
table
Solution
The simplest and most effective solution is to use a Discourse theme component for client-side repair. This doesn’t require any database operations or server configuration changes. It only involves adding a short JavaScript code snippet that automatically replaces the incorrect URLs with the correct ones in the browser.
Theme Component Code
import { apiInitializer } from "discourse/lib/api";
export default apiInitializer("0.11.1", (api) => {
// Fix already loaded images
function fixImageUrls() {
const badDomain = "info.7a4081a2d83d3f43fe6b1be1c926fd1c.r2.cloudflarestorage.com";
const goodDomain = "store.starorigin.cc";
// Fix regular images
document.querySelectorAll(`img[src*="${badDomain}"]`).forEach(img => {
img.src = img.src.replace(badDomain, goodDomain);
});
// Fix lazy-loaded images
document.querySelectorAll(`img[data-src*="${badDomain}"]`).forEach(img => {
img.setAttribute('data-src', img.getAttribute('data-src').replace(badDomain, goodDomain));
});
// Fix background images
document.querySelectorAll('[style*="background"]').forEach(el => {
if (el.style.backgroundImage && el.style.backgroundImage.includes(badDomain)) {
el.style.backgroundImage = el.style.backgroundImage.replace(badDomain, goodDomain);
}
});
// Fix various other potential attributes
['srcset', 'data-large-src', 'data-small-src', 'data-download-href'].forEach(attr => {
document.querySelectorAll(`[${attr}*="${badDomain}"]`).forEach(el => {
el.setAttribute(attr, el.getAttribute(attr).replace(badDomain, goodDomain));
});
});
}
// Fix images in the editor
api.decorateCooked($elem => {
fixImageUrls();
}, { id: 'fix-r2-image-urls' });
// Fix after initial load
api.onPageChange(() => {
fixImageUrls();
});
// Handle dynamically loaded content
const observer = new MutationObserver(mutations => {
fixImageUrls();
});
// Start observing after the DOM is loaded
if (document.readyState === "loading") {
document.addEventListener('DOMContentLoaded', () => {
fixImageUrls();
startObserver();
});
} else {
fixImageUrls();
startObserver();
}
function startObserver() {
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['src', 'data-src', 'srcset', 'style']
});
}
});
How the Code Works
This code performs the following actions:
- Comprehensive Detection: Finds all image URLs containing the incorrect domain.
- Multiple Element Handling: Handles various image elements and attributes (img tags, lazy-loaded images, background images, etc.).
- Dynamic Monitoring: Uses a
MutationObserver
to monitor page changes, ensuring dynamically loaded content is also fixed. - Discourse Integration: Integrates with the Discourse API to handle various special scenarios.
Installation Steps
- Log in to your Discourse administrator account.
- Go to Admin > Customize > Theme Components.
- Click the New button.
- Select the Create new component option.
- Name it “Fix R2 Image URLs” (or any name you prefer).
- In the “Javascript” tab, paste the code above.
- Click the Create button.
- Click the Enable button and choose the theme to apply it to (usually “Default”).
Verification
After installation:
- Refresh the forum page.
- View posts containing images.
- Confirm that thumbnails are displayed correctly.
- Use your browser’s developer tools to check that image requests are all pointing to the CDN domain.
While a client-side solution is the simplest, fastest, and lowest-risk approach, especially when direct server access is limited.
Conclusion
This simple theme component effectively addresses the image URL issue when integrating Discourse with Cloudflare R2 storage, without requiring server changes or complex configurations. Although it fixes the problem on the client-side rather than addressing the root cause, it’s easy to implement, provides immediate results, and is an ideal solution.
If your Discourse site is also experiencing similar issues, feel free to try this solution.