Skip to main content

How to set up RSS Feeds that work in production without CORS failures?

Hi everyone, please has anyone used #113 - RSS Feeds? I tried setting it up and it works on staging but when it goes live, it pops up the error below on screen.

Failed to load RSS feed from https://us13.campaign-archive.com/feed?u=996c504cc2f36a3a42e7101dc&id=3a2c553b44. Error: All CORS proxies failed

I don’t have access to sandbox but here’s the read-only

And the RSS feed is on a page titled “The Latest”

7 comments

  • A J
    A J

    cc: Magnaem from Memberstack this memberscript might need some tweaks to avoid the error.

    0
  • Magnaem A
    Magnaem A

    Hi David Afolayan 👋  

    Do you by any chance have an ad blockers switch on for your site/browser? Ad blockers usually block the CORS proxy services. These proxies are often on blocklists because they can be used to circumvent privacy protections. I've switched my ad blocker off and this error disappeared. (screenshot)

    Can you use this script and let me know if this works for you:

    <!-- 💙 MEMBERSCRIPT #113 v0.3 💙 - RSS FEEDS IN WEBFLOW -->
    <script>
    (function() {
      // More reliable CORS proxies, tried in order of reliability
      const CORS_PROXIES = [
        'https://api.allorigins.win/raw?url=',
        'https://api.codetabs.com/v1/proxy?quest=',
        'https://corsproxy.io/?',
      ];
    
      function loadScript(src, onLoad, onError) {
        const script = document.createElement('script');
        script.src = src;
        script.onload = onLoad;
        script.onerror = onError;
        document.head.appendChild(script);
      }
    
      async function fetchWithFallback(url) {
        // Try direct fetch first (works if feed supports CORS)
        try {
          const directResponse = await fetch(url);
          if (directResponse.ok) {
            return await directResponse.text();
          }
        } catch (error) {
          console.log('Direct fetch failed, trying proxies...');
        }
    
        // Try each proxy with a timeout
        for (const proxy of CORS_PROXIES) {
          try {
            const controller = new AbortController();
            const timeoutId = setTimeout(() => controller.abort(), 8000); // 8 second timeout
            
            const response = await fetch(proxy + encodeURIComponent(url), {
              signal: controller.signal
            });
            
            clearTimeout(timeoutId);
            
            if (response.ok) {
              return await response.text();
            }
          } catch (error) {
            console.warn(`Failed to fetch with proxy ${proxy}:`, error.name);
          }
        }
        throw new Error('Unable to load feed');
      }
    
      function initRSSFeed() {
        if (typeof RSSParser === 'undefined') {
          console.error('RSSParser is not defined.');
          return;
        }
    
        const parser = new RSSParser({
          customFields: {
            item: [
              ['media:content', 'mediaContent', {keepArray: true}],
              ['media:thumbnail', 'mediaThumbnail', {keepArray: true}],
              ['enclosure', 'enclosure', {keepArray: true}],
            ]
          }
        });
    
        document.querySelectorAll('[ms-code-rss-feed]').forEach(element => {
          const url = element.getAttribute('ms-code-rss-url');
          const limit = parseInt(element.getAttribute('ms-code-rss-limit')) || 5;
    
          // Store template before clearing
          const templateItem = element.querySelector('[ms-code-rss-item]');
          if (!templateItem) {
            console.error('No template item found with [ms-code-rss-item] attribute');
            return;
          }
          const template = templateItem.cloneNode(true);
    
          // Show loading state
          const loadingMessage = element.getAttribute('ms-code-rss-loading') || 'Loading feed...';
          element.innerHTML = `<div style="padding: 20px; text-align: center; color: #666;">${loadingMessage}</div>`;
    
          fetchWithFallback(url)
            .then(str => parser.parseString(str))
            .then(feed => {
              renderRSSItems(element, template, feed.items.slice(0, limit), {
                showImage: element.getAttribute('ms-code-rss-show-image') !== 'false',
                showDate: element.getAttribute('ms-code-rss-show-date') !== 'false',
                dateFormat: element.getAttribute('ms-code-rss-date-format') || 'short',
                target: element.getAttribute('ms-code-rss-target') || '_self'
              });
            })
            .catch(err => {
              console.error('Error fetching or parsing RSS feed:', err);
              // User-friendly error message
              const errorMessage = element.getAttribute('ms-code-rss-error') || 
                'Unable to load feed. Please try disabling your ad blocker or check back later.';
              element.innerHTML = `<div style="padding: 20px; text-align: center; color: #999; font-size: 14px;">${errorMessage}</div>`;
            });
        });
      }
    
      function renderRSSItems(element, template, items, options) {
        element.innerHTML = ''; // Clear loading message
    
        items.forEach(item => {
          const itemElement = template.cloneNode(true);
    
          const title = itemElement.querySelector('[ms-code-rss-title]');
          if (title) {
            const titleLength = parseInt(title.getAttribute('ms-code-rss-title-length')) || Infinity;
            title.textContent = truncate(item.title, titleLength);
          }
    
          const description = itemElement.querySelector('[ms-code-rss-description]');
          if (description) {
            const descriptionLength = parseInt(description.getAttribute('ms-code-rss-description-length')) || Infinity;
            description.textContent = truncate(stripHtml(item.content || item.description), descriptionLength);
          }
    
          const date = itemElement.querySelector('[ms-code-rss-date]');
          if (date && options.showDate && item.pubDate) {
            date.textContent = formatDate(new Date(item.pubDate), options.dateFormat);
          }
    
          const img = itemElement.querySelector('[ms-code-rss-image]');
          if (img && options.showImage) {
            const imgUrl = getImageUrl(item);
            if (imgUrl) {
              img.src = imgUrl;
              img.alt = item.title;
              img.removeAttribute('srcset');
            } else if (img.tagName === 'IMG') {
              img.style.display = 'none'; // Hide if no image found
            }
          }
    
          const linkElement = itemElement.querySelector('[ms-code-rss-link]');
          if (linkElement) {
            linkElement.setAttribute('href', item.link);
            linkElement.setAttribute('target', options.target);
          }
    
          element.appendChild(itemElement);
        });
      }
    
      function getImageUrl(item) {
        const sources = ['mediaContent', 'mediaThumbnail', 'enclosure'];
        for (let source of sources) {
          if (item[source] && item[source][0]) {
            return item[source][0].$ ? item[source][0].$.url : item[source][0].url;
          }
        }
        return null;
      }
    
      function truncate(str, length) {
        if (!str) return '';
        if (length === Infinity) return str;
        return str.length > length ? str.slice(0, length) + '...' : str;
      }
    
      function stripHtml(html) {
        const tmp = document.createElement('DIV');
        tmp.innerHTML = html || '';
        return tmp.textContent || tmp.innerText || '';
      }
    
      function formatDate(date, format) {
        if (!(date instanceof Date) || isNaN(date)) return '';
        const options = format === 'long' ? 
              { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' } : 
        undefined;
        return format === 'relative' ? getRelativeTimeString(date) : date.toLocaleDateString(undefined, options);
      }
    
      function getRelativeTimeString(date, lang = navigator.language) {
        const timeMs = date.getTime();
        const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
        const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
        const units = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
        const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
        const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
        const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' });
        return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
      }
    
      loadScript('https://cdn.jsdelivr.net/npm/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
        console.error('Error loading RSS Parser script');
        loadScript('https://unpkg.com/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
          console.error('Error loading RSS Parser script from backup CDN');
        });
      });
    
    })();
    </script>

     

     

    0
  • David Afolayan
    David Afolayan OP

    Alright, thank you, I’ll tried this.

    Magnaem from Memberstack It works, thank you!! 🥹
    The images loaded on your end? the don’t seem to load on mine

    The load time is also really long 😮‍💨

    Is there anything I can do to adjust the load time?

    We don’t have any adblockers that I’m aware of, but I’ll also confirm.

    0
  • Magnaem A
    Magnaem A

    Hi David Afolayan, I've slightly optimized the script to wait 3–4 seconds per attempt instead of 8, save the feed for 5 minutes so repeat visits are instant, load multiple feeds simultaneously, load images only when they’re about to be visible, and try the direct sources first before using backup proxies.

    Together, this reduces load time from 8–24 seconds to about 3–5 seconds. Let me know if this works for you:

    <!-- 💙 MEMBERSCRIPT #113 v0.3 💙 - RSS FEEDS IN WEBFLOW -->
    <script>
    (function() {
      'use strict';
    
      // More reliable CORS proxies - reduced list for faster fallback
      const CORS_PROXIES = [
        'https://api.allorigins.win/raw?url=',
        'https://api.codetabs.com/v1/proxy?quest=',
      ];
    
      // Cache for RSS feeds (5 minute cache)
      const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
      const feedCache = new Map();
    
      function loadScript(src, onLoad, onError) {
        const script = document.createElement('script');
        script.src = src;
        script.onload = onLoad;
        script.onerror = onError;
        document.head.appendChild(script);
      }
    
      // Fast fetch with timeout and caching
      async function fetchWithTimeout(url, timeout = 5000) {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeout);
        
        try {
          const response = await fetch(url, { signal: controller.signal });
          clearTimeout(timeoutId);
          if (response.ok) {
            return await response.text();
          }
        } catch (error) {
          clearTimeout(timeoutId);
          throw error;
        }
        throw new Error('Request failed');
      }
    
      // Parallel proxy attempts for faster response
      async function fetchWithFallback(url) {
        // Check cache first
        const cacheKey = url;
        const cached = feedCache.get(cacheKey);
        if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
          return cached.data;
        }
    
        // Try direct fetch first (fastest if it works)
        const directPromise = fetchWithTimeout(url, 3000).catch(() => null);
    
        // Try proxies in parallel (race condition - first to succeed wins)
        const proxyPromises = CORS_PROXIES.map(proxy => 
          fetchWithTimeout(proxy + encodeURIComponent(url), 4000)
            .catch(() => null)
        );
    
        // Race all requests - first successful one wins
        const allPromises = [directPromise, ...proxyPromises];
        
        for (const promise of allPromises) {
          try {
            const result = await promise;
            if (result) {
              // Cache the result
              feedCache.set(cacheKey, {
                data: result,
                timestamp: Date.now()
              });
              return result;
            }
          } catch (e) {
            // Continue to next promise
          }
        }
    
        throw new Error('Unable to load feed');
      }
    
      function initRSSFeed() {
        if (typeof RSSParser === 'undefined') {
          console.error('RSSParser is not defined.');
          return;
        }
    
        const parser = new RSSParser({
          customFields: {
            item: [
              ['media:content', 'mediaContent', {keepArray: true}],
              ['media:thumbnail', 'mediaThumbnail', {keepArray: true}],
              ['enclosure', 'enclosure', {keepArray: true}],
            ]
          }
        });
    
        // Process all feeds in parallel for faster overall load
        const feedPromises = Array.from(document.querySelectorAll('[ms-code-rss-feed]')).map(element => {
          const url = element.getAttribute('ms-code-rss-url');
          if (!url) {
            console.error('No RSS URL specified');
            return Promise.resolve();
          }
    
          const limit = parseInt(element.getAttribute('ms-code-rss-limit')) || 5;
    
          // Store template before clearing
          const templateItem = element.querySelector('[ms-code-rss-item]');
          if (!templateItem) {
            console.error('No template item found with [ms-code-rss-item] attribute');
            return Promise.resolve();
          }
          const template = templateItem.cloneNode(true);
    
          // Show loading state
          const loadingMessage = element.getAttribute('ms-code-rss-loading') || 'Loading feed...';
          element.innerHTML = `<div style="padding: 20px; text-align: center; color: #666;">${loadingMessage}</div>`;
    
          return fetchWithFallback(url)
            .then(str => parser.parseString(str))
            .then(feed => {
              renderRSSItems(element, template, feed.items.slice(0, limit), {
                showImage: element.getAttribute('ms-code-rss-show-image') !== 'false',
                showDate: element.getAttribute('ms-code-rss-show-date') !== 'false',
                dateFormat: element.getAttribute('ms-code-rss-date-format') || 'short',
                target: element.getAttribute('ms-code-rss-target') || '_self'
              });
            })
            .catch(err => {
              console.error('Error fetching or parsing RSS feed:', err);
              // User-friendly error message
              const errorMessage = element.getAttribute('ms-code-rss-error') || 
                'Unable to load feed. Please try disabling your ad blocker or check back later.';
              element.innerHTML = `<div style="padding: 20px; text-align: center; color: #999; font-size: 14px;">${errorMessage}</div>`;
            });
        });
    
        // Wait for all feeds to load (or fail)
        return Promise.allSettled(feedPromises);
      }
    
      function renderRSSItems(element, template, items, options) {
        element.innerHTML = ''; // Clear loading message
    
        items.forEach(item => {
          const itemElement = template.cloneNode(true);
    
          const title = itemElement.querySelector('[ms-code-rss-title]');
          if (title) {
            const titleLength = parseInt(title.getAttribute('ms-code-rss-title-length')) || Infinity;
            title.textContent = truncate(item.title, titleLength);
          }
    
          const description = itemElement.querySelector('[ms-code-rss-description]');
          if (description) {
            const descriptionLength = parseInt(description.getAttribute('ms-code-rss-description-length')) || Infinity;
            description.textContent = truncate(stripHtml(item.content || item.description), descriptionLength);
          }
    
          const date = itemElement.querySelector('[ms-code-rss-date]');
          if (date && options.showDate && item.pubDate) {
            date.textContent = formatDate(new Date(item.pubDate), options.dateFormat);
          }
    
          const img = itemElement.querySelector('[ms-code-rss-image]');
          if (img && options.showImage) {
            const imgUrl = getImageUrl(item);
            if (imgUrl) {
              img.src = imgUrl;
              img.alt = item.title;
              img.removeAttribute('srcset');
              // Lazy load images for better performance
              img.loading = 'lazy';
            } else if (img.tagName === 'IMG') {
              img.style.display = 'none';
            }
          }
    
          const linkElement = itemElement.querySelector('[ms-code-rss-link]');
          if (linkElement) {
            linkElement.setAttribute('href', item.link);
            linkElement.setAttribute('target', options.target);
          }
    
          element.appendChild(itemElement);
        });
      }
    
      function getImageUrl(item) {
        const sources = ['mediaContent', 'mediaThumbnail', 'enclosure'];
        for (let source of sources) {
          if (item[source] && item[source][0]) {
            return item[source][0].$ ? item[source][0].$.url : item[source][0].url;
          }
        }
        return null;
      }
    
      function truncate(str, length) {
        if (!str) return '';
        if (length === Infinity) return str;
        return str.length > length ? str.slice(0, length) + '...' : str;
      }
    
      function stripHtml(html) {
        const tmp = document.createElement('DIV');
        tmp.innerHTML = html || '';
        return tmp.textContent || tmp.innerText || '';
      }
    
      function formatDate(date, format) {
        if (!(date instanceof Date) || isNaN(date)) return '';
        const options = format === 'long' ? 
              { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' } : 
        undefined;
        return format === 'relative' ? getRelativeTimeString(date) : date.toLocaleDateString(undefined, options);
      }
    
      function getRelativeTimeString(date, lang = navigator.language) {
        const timeMs = date.getTime();
        const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
        const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
        const units = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
        const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
        const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
        const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' });
        return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
      }
    
      // Preload RSS parser script early
      loadScript('https://cdn.jsdelivr.net/npm/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
        console.error('Error loading RSS Parser script');
        loadScript('https://unpkg.com/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
          console.error('Error loading RSS Parser script from backup CDN');
        });
      });
    
    })();
    </script>
    
    0
  • David Afolayan
    David Afolayan OP

    Hey Magnaem from Memberstack, I’m really grateful for your assistance with this, but it still has a few “bugs” even on my client’s end.

    It didn’t used to get blocked by ad blockers + the images still don’t show up.

    Is that still something the team can help with?

    0
  • Julian Galluzzo
    Julian Galluzzo

    Hey David Afolayan! Have you tried putting the script into any AI tool you're used to (Claude, ChatGPT, whatever) to try and work out the bugs? Also keep an eye on the console logs, they'll probably reveal where the issue is coming from.

    It's tough for us to make a one size fits all script, especially for RSS feeds 😅 That's why we give you the script code so that you can iterate on it in a way that works for you

    0
  • David Afolayan
    David Afolayan OP

    Fun fact, that’s what we eventually did 😮‍💨 with help from one of our devs to clean up the code.

    Thank y’all so much for the script and all the help though!

    0

Please sign in to leave a comment.

Sitemap